diff --git a/app/src/debug/res/drawable/ic_download_filled.xml b/app/src/debug/res/drawable/ic_download_filled.xml
new file mode 100644
index 00000000..16e367f8
--- /dev/null
+++ b/app/src/debug/res/drawable/ic_download_filled.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 8daaf9e6..81895a50 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,8 +3,6 @@
package="dev.jdtech.jellyfin">
-
diff --git a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
index e105bb58..0e38ca67 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/MainActivity.kt
@@ -13,6 +13,7 @@ import com.google.android.material.bottomnavigation.BottomNavigationView
import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.databinding.ActivityMainAppBinding
import dev.jdtech.jellyfin.fragments.HomeFragmentDirections
+import dev.jdtech.jellyfin.utils.loadDownloadLocation
import dev.jdtech.jellyfin.viewmodels.MainViewModel
@AndroidEntryPoint
@@ -57,6 +58,8 @@ class MainActivity : AppCompatActivity() {
if (destination.id == R.id.about_libraries_dest) binding.mainToolbar.title = getString(R.string.app_info)
}
+ loadDownloadLocation(applicationContext)
+
viewModel.navigateToAddServer.observe(this, {
if (it) {
navController.navigate(HomeFragmentDirections.actionHomeFragmentToAddServerFragment())
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt
index 26043ced..dffa0c9a 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt
@@ -94,6 +94,15 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
binding.favoriteButton.setImageResource(drawable)
})
+ viewModel.downloaded.observe(viewLifecycleOwner, {
+ val drawable = when (it) {
+ true -> R.drawable.ic_download_filled
+ false -> R.drawable.ic_download
+ }
+
+ binding.downloadButton.setImageResource(drawable)
+ })
+
viewModel.downloadEpisode.observe(viewLifecycleOwner, {
if (it) {
requestDownload(Uri.parse(viewModel.downloadRequestItem.uri), viewModel.downloadRequestItem, this)
diff --git a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt
index 2e48399e..7746500e 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/fragments/MediaInfoFragment.kt
@@ -129,6 +129,15 @@ class MediaInfoFragment : Fragment() {
binding.favoriteButton.setImageResource(drawable)
})
+ viewModel.downloaded.observe(viewLifecycleOwner, {
+ val drawable = when (it) {
+ true -> R.drawable.ic_download_filled
+ false -> R.drawable.ic_download
+ }
+
+ binding.downloadButton.setImageResource(drawable)
+ })
+
binding.trailerButton.setOnClickListener {
if (viewModel.item.value?.remoteTrailers.isNullOrEmpty()) return@setOnClickListener
val intent = Intent(
diff --git a/app/src/main/java/dev/jdtech/jellyfin/models/DownloadMetadata.kt b/app/src/main/java/dev/jdtech/jellyfin/models/DownloadMetadata.kt
index b61fd432..49a41d82 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/models/DownloadMetadata.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/models/DownloadMetadata.kt
@@ -14,5 +14,7 @@ data class DownloadMetadata(
val indexNumber: Int? = null,
val playbackPosition: Long? = null,
val playedPercentage: Double? = null,
- val seriesId: UUID? = null
+ val seriesId: UUID? = null,
+ val played: Boolean? = null,
+ val overview: String? = null
) : Parcelable
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/utils/DownloadUtilities.kt b/app/src/main/java/dev/jdtech/jellyfin/utils/DownloadUtilities.kt
index 60047c8e..1b591b63 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/utils/DownloadUtilities.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/utils/DownloadUtilities.kt
@@ -1,17 +1,11 @@
package dev.jdtech.jellyfin.utils
-import android.Manifest
import android.app.DownloadManager
import android.content.Context
-import android.content.pm.PackageManager
import android.net.Uri
-import android.os.Build
import android.os.Environment
-import androidx.core.app.ActivityCompat
-import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
-import dev.jdtech.jellyfin.R
import dev.jdtech.jellyfin.models.DownloadMetadata
import dev.jdtech.jellyfin.models.DownloadRequestItem
import dev.jdtech.jellyfin.models.PlayerItem
@@ -22,46 +16,9 @@ import timber.log.Timber
import java.io.File
import java.util.UUID
+var defaultStorage: File? = null
+
fun requestDownload(uri: Uri, downloadRequestItem: DownloadRequestItem, context: Fragment) {
- // Storage permission for downloads isn't necessary from Android 10 onwards
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
- @Suppress("MagicNumber")
- Timber.d("REQUESTING PERMISSION")
-
- if (ContextCompat.checkSelfPermission(
- context.requireActivity(),
- Manifest.permission.WRITE_EXTERNAL_STORAGE
- ) != PackageManager.PERMISSION_GRANTED
- ) {
- if (ActivityCompat.shouldShowRequestPermissionRationale(
- context.requireActivity(),
- Manifest.permission.WRITE_EXTERNAL_STORAGE
- )
- ) {
- ActivityCompat.requestPermissions(
- context.requireActivity(),
- arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1
- )
- } else {
- ActivityCompat.requestPermissions(
- context.requireActivity(),
- arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), 1
- )
- }
- }
-
- val granted = ContextCompat.checkSelfPermission(
- context.requireActivity(),
- Manifest.permission.WRITE_EXTERNAL_STORAGE
- ) == PackageManager.PERMISSION_GRANTED
-
- if (!granted) {
- context.requireContext().toast(R.string.download_no_storage_permission)
- return
- }
- }
- val defaultStorage = getDownloadLocation(context.requireContext())
- Timber.d(defaultStorage.toString())
val downloadRequest = DownloadManager.Request(uri)
.setTitle(downloadRequestItem.metadata.name)
.setDescription("Downloading")
@@ -75,16 +32,13 @@ fun requestDownload(uri: Uri, downloadRequestItem: DownloadRequestItem, context:
)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
if (!File(defaultStorage, downloadRequestItem.itemId.toString()).exists())
- downloadFile(downloadRequest, 1, context.requireContext())
+ downloadFile(downloadRequest, context.requireContext())
createMetadataFile(
downloadRequestItem.metadata,
- downloadRequestItem.itemId,
- context.requireContext()
- )
+ downloadRequestItem.itemId)
}
-private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context: Context) {
- val defaultStorage = getDownloadLocation(context)
+private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID) {
val metadataFile = File(defaultStorage, "${itemId}.metadata")
metadataFile.writeText("") //This might be necessary to make sure that the metadata file is empty
@@ -100,6 +54,8 @@ private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context
out.println(metadata.playbackPosition.toString())
out.println(metadata.playedPercentage.toString())
out.println(metadata.seriesId.toString())
+ out.println(metadata.played.toString())
+ out.println(if (metadata.overview != null) metadata.overview.replace("\n", "\\n") else "")
}
} else if (metadata.type == "Movie") {
metadataFile.printWriter().use { out ->
@@ -108,13 +64,14 @@ private fun createMetadataFile(metadata: DownloadMetadata, itemId: UUID, context
out.println(metadata.name.toString())
out.println(metadata.playbackPosition.toString())
out.println(metadata.playedPercentage.toString())
+ out.println(metadata.played.toString())
+ out.println(if (metadata.overview != null) metadata.overview.replace("\n", "\\n") else "")
}
}
}
-private fun downloadFile(request: DownloadManager.Request, downloadMethod: Int, context: Context) {
- require(downloadMethod >= 0) { "Download method hasn't been set" }
+private fun downloadFile(request: DownloadManager.Request, context: Context) {
request.apply {
setAllowedOverMetered(false)
setAllowedOverRoaming(false)
@@ -122,13 +79,12 @@ private fun downloadFile(request: DownloadManager.Request, downloadMethod: Int,
context.getSystemService()?.enqueue(request)
}
-private fun getDownloadLocation(context: Context): File? {
- return context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
+fun loadDownloadLocation(context: Context) {
+ defaultStorage = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
}
-fun loadDownloadedEpisodes(context: Context): List {
+fun loadDownloadedEpisodes(): List {
val items = mutableListOf()
- val defaultStorage = getDownloadLocation(context)
defaultStorage?.walk()?.forEach {
if (it.isFile && it.extension == "") {
try {
@@ -154,6 +110,29 @@ fun loadDownloadedEpisodes(context: Context): List {
return items.toList()
}
+fun itemIsDownloaded(itemId: UUID): Boolean {
+ val file = File(defaultStorage!!, itemId.toString())
+ if (file.isFile && file.extension == "") {
+ if (File(defaultStorage, "${itemId}.metadata").exists()){
+ return true
+ }
+ }
+ return false
+}
+
+fun getDownloadPlayerItem(itemId: UUID): PlayerItem? {
+ val file = File(defaultStorage!!, itemId.toString())
+ try{
+ val metadataFile = File(defaultStorage, "${file.name}.metadata").readLines()
+ val metadata = parseMetadataFile(metadataFile)
+ return PlayerItem(metadata.name, UUID.fromString(file.name), "", metadata.playbackPosition!!, file.absolutePath, metadata)
+ } catch (e: Exception) {
+ file.delete()
+ Timber.e(e)
+ }
+ return null
+}
+
fun deleteDownloadedEpisode(uri: String) {
try {
File(uri).delete()
@@ -175,7 +154,7 @@ fun postDownloadPlaybackProgress(uri: String, playbackPosition: Long, playedPerc
metadataArray[3] = playbackPosition.toString()
metadataArray[4] = playedPercentage.toString()
}
-
+ Timber.d("PLAYEDPERCENTAGE $playedPercentage")
metadataFile.writeText("") //This might be necessary to make sure that the metadata file is empty
metadataFile.printWriter().use { out ->
metadataArray.forEach {
@@ -204,7 +183,8 @@ fun downloadMetadataToBaseItemDto(metadata: DownloadMetadata): BaseItemDto {
parentIndexNumber = metadata.parentIndexNumber,
indexNumber = metadata.indexNumber,
userData = userData,
- seriesId = metadata.seriesId
+ seriesId = metadata.seriesId,
+ overview = metadata.overview
)
}
@@ -218,7 +198,9 @@ fun baseItemDtoToDownloadMetadata(item: BaseItemDto): DownloadMetadata {
indexNumber = item.indexNumber,
playbackPosition = item.userData?.playbackPositionTicks ?: 0,
playedPercentage = item.userData?.playedPercentage,
- seriesId = item.seriesId
+ seriesId = item.seriesId,
+ played = item.userData?.played,
+ overview = item.overview
)
}
@@ -237,7 +219,9 @@ fun parseMetadataFile(metadataFile: List): DownloadMetadata {
} else {
metadataFile[7].toDouble()
},
- seriesId = UUID.fromString(metadataFile[8])
+ seriesId = UUID.fromString(metadataFile[8]),
+ played = metadataFile[9].toBoolean(),
+ overview = metadataFile[10].replace("\\n", "\n")
)
} else {
return DownloadMetadata(
@@ -250,12 +234,14 @@ fun parseMetadataFile(metadataFile: List): DownloadMetadata {
} else {
metadataFile[4].toDouble()
},
+ played = metadataFile[5].toBoolean(),
+ overview = metadataFile[6].replace("\\n", "\n")
)
}
}
-suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository, context: Context) {
- val items = loadDownloadedEpisodes(context)
+suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository) {
+ val items = loadDownloadedEpisodes()
items.forEach() {
try {
val localPlaybackProgress = it.metadata?.playbackPosition
@@ -268,6 +254,10 @@ suspend fun syncPlaybackProgress(jellyfinRepository: JellyfinRepository, context
var playbackProgress: Long = 0
var playedPercentage = 0.0
+ if (it.metadata?.played == true || item.userData?.played == true){
+ return@forEach
+ }
+
if (localPlaybackProgress != null) {
if (localPlaybackProgress > playbackProgress) {
playbackProgress = localPlaybackProgress
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadViewModel.kt
index c8166bdc..7f84f090 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadViewModel.kt
@@ -1,28 +1,18 @@
package dev.jdtech.jellyfin.viewmodels
import android.annotation.SuppressLint
-import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
-import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.DownloadSection
-import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.loadDownloadedEpisodes
-import dev.jdtech.jellyfin.utils.postDownloadPlaybackProgress
import kotlinx.coroutines.*
import kotlinx.coroutines.launch
import timber.log.Timber
import java.util.*
-import javax.inject.Inject
-@HiltViewModel
-class DownloadViewModel
-@Inject
-constructor(
- private val application: Application,
-) : ViewModel() {
+class DownloadViewModel : ViewModel() {
private val _downloadSections = MutableLiveData>()
val downloadSections: LiveData> = _downloadSections
@@ -42,7 +32,7 @@ constructor(
_finishedLoading.value = false
viewModelScope.launch {
try {
- val items = loadDownloadedEpisodes(application)
+ val items = loadDownloadedEpisodes()
if (items.isEmpty()) {
_downloadSections.value = listOf()
_finishedLoading.value = true
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt
index e5e8825d..3f279421 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt
@@ -13,6 +13,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.baseItemDtoToDownloadMetadata
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
+import dev.jdtech.jellyfin.utils.itemIsDownloaded
import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.ItemFields
@@ -46,6 +47,9 @@ constructor(
private val _favorite = MutableLiveData()
val favorite: LiveData = _favorite
+ private val _downloaded = MutableLiveData()
+ val downloaded: LiveData = _downloaded
+
private val _downloadEpisode = MutableLiveData()
val downloadEpisode: LiveData = _downloadEpisode
@@ -56,6 +60,7 @@ constructor(
fun loadEpisode(episodeId: UUID) {
viewModelScope.launch {
try {
+ _downloaded.value = itemIsDownloaded(episodeId)
val item = jellyfinRepository.getItem(episodeId)
_item.value = item
_runTime.value = "${item.runTimeTicks?.div(600000000)} min"
@@ -129,5 +134,6 @@ constructor(
fun doneDownloadEpisode() {
_downloadEpisode.value = false
+ _downloaded.value = true
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
index 19248ada..2663c4ef 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/HomeViewModel.kt
@@ -27,7 +27,7 @@ import javax.inject.Inject
@HiltViewModel
class HomeViewModel @Inject internal constructor(
- private val application: Application,
+ application: Application,
private val repository: JellyfinRepository
) : ViewModel() {
@@ -67,7 +67,7 @@ class HomeViewModel @Inject internal constructor(
views.postValue(updated)
withContext(Dispatchers.Default) {
- syncPlaybackProgress(repository, application)
+ syncPlaybackProgress(repository)
}
state.tryEmit(Loading(inProgress = false))
} catch (e: Exception) {
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt
index c16f8238..42f70ed8 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/MediaInfoViewModel.kt
@@ -12,6 +12,7 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.baseItemDtoToDownloadMetadata
import dev.jdtech.jellyfin.utils.deleteDownloadedEpisode
import dev.jdtech.jellyfin.utils.downloadMetadataToBaseItemDto
+import dev.jdtech.jellyfin.utils.itemIsDownloaded
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -61,6 +62,9 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
private val _favorite = MutableLiveData()
val favorite: LiveData = _favorite
+ private val _downloaded = MutableLiveData()
+ val downloaded: LiveData = _downloaded
+
private val _error = MutableLiveData()
val error: LiveData = _error
@@ -75,6 +79,7 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
_error.value = null
viewModelScope.launch {
try {
+ _downloaded.value = itemIsDownloaded(itemId)
_item.value = jellyfinRepository.getItem(itemId)
_actors.value = getActors(_item.value!!)
_director.value = getDirector(_item.value!!)
@@ -201,5 +206,6 @@ constructor(private val jellyfinRepository: JellyfinRepository) : ViewModel() {
fun doneDownloadMedia() {
_downloadMedia.value = false
+ _downloaded.value = true
}
}
\ No newline at end of file
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt
index ed982f9e..ee1ccd82 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt
@@ -107,6 +107,7 @@ constructor(
item.mediaSourceUri.isNotEmpty() -> item.mediaSourceUri
else -> jellyfinRepository.getStreamUrl(item.itemId, item.mediaSourceId)
}
+ playFromDownloads = item.mediaSourceUri.isNotEmpty()
Timber.d("Stream url: $streamUrl")
val mediaItem =
@@ -156,17 +157,17 @@ constructor(
override fun run() {
viewModelScope.launch {
if (player.currentMediaItem != null) {
- try {
- jellyfinRepository.postPlaybackProgress(
- UUID.fromString(player.currentMediaItem!!.mediaId),
- player.currentPosition.times(10000),
- !player.isPlaying
- )
- } catch (e: Exception) {
- Timber.e(e)
- }
if(playFromDownloads){
- postDownloadPlaybackProgress(items[0].mediaSourceUri, player.currentPosition, (player.currentPosition.toDouble()/player.duration.toDouble()).times(100)) //TODO Automaticcaly use the correct item
+ postDownloadPlaybackProgress(items[0].mediaSourceUri, player.currentPosition, (player.currentPosition.toDouble()/player.duration.toDouble()).times(100)) //TODO Automatically use the correct item
+ }
+ try {
+ jellyfinRepository.postPlaybackProgress(
+ UUID.fromString(player.currentMediaItem!!.mediaId),
+ player.currentPosition.times(10000),
+ !player.isPlaying
+ )
+ } catch (e: Exception) {
+ Timber.e(e)
}
}
}
diff --git a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerViewModel.kt b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerViewModel.kt
index 824d7f7c..36256014 100644
--- a/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerViewModel.kt
+++ b/app/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerViewModel.kt
@@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.repository.JellyfinRepository
+import dev.jdtech.jellyfin.utils.getDownloadPlayerItem
+import dev.jdtech.jellyfin.utils.itemIsDownloaded
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collect
@@ -35,8 +37,15 @@ class PlayerViewModel @Inject internal constructor(
fun loadPlayerItems(
item: BaseItemDto,
mediaSourceIndex: Int = 0,
- onVersionSelectRequired: () -> Unit = { Unit }
+ onVersionSelectRequired: () -> Unit = { }
) {
+ if (itemIsDownloaded(item.id)) {
+ val playerItem = getDownloadPlayerItem(item.id)
+ if (playerItem != null) {
+ loadOfflinePlayerItems(playerItem)
+ return
+ }
+ }
Timber.d("Loading player items for item ${item.id}")
if (item.mediaSources.orEmpty().size > 1) {
onVersionSelectRequired()
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6edba159..248f6c66 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -79,7 +79,6 @@
Force software decoding
Disable hardware decoding and use software decoding. Can be useful if hardware decoding gives weird artifacts.
Download
- Cannot download files without storage permissions
Delete
Person Detail
Detail unavailable