feat: Download Season + Download unwatched episodes only dialogue
This commit is contained in:
parent
9baa84e1e7
commit
e987ac477d
15 changed files with 186 additions and 34 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@ local.properties
|
|||
|
||||
# Android Studio generated files and folders
|
||||
captures/
|
||||
app/phone/libre/release/baselineProfiles
|
||||
.kotlin
|
||||
.externalNativeBuild/
|
||||
.cxx/
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -16,36 +16,10 @@
|
|||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 4,
|
||||
"versionCode": 5,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-armeabi-v7a.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "x86_64"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 4,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-x86_64.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "arm64-v8a"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 4,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-arm64-v8a.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
|
@ -55,9 +29,35 @@
|
|||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 4,
|
||||
"versionCode": 5,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-x86.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "arm64-v8a"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 5,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-arm64-v8a.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "x86_64"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 5,
|
||||
"versionName": "0.14.2",
|
||||
"outputFile": "ananas-v0.14.2-libre-x86_64.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
|
@ -67,9 +67,9 @@
|
|||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-armeabi-v7a.dm",
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-x86_64.dm",
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-x86.dm",
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-arm64-v8a.dm",
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-x86.dm"
|
||||
"baselineProfiles/1/ananas-v0.14.2-libre-x86_64.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -77,9 +77,9 @@
|
|||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-armeabi-v7a.dm",
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-x86_64.dm",
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-x86.dm",
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-arm64-v8a.dm",
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-x86.dm"
|
||||
"baselineProfiles/0/ananas-v0.14.2-libre-x86_64.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
@ -2,8 +2,13 @@ package com.nomadics9.ananas.fragments
|
|||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.MenuHost
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
|
@ -16,6 +21,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import com.nomadics9.ananas.adapters.EpisodeListAdapter
|
||||
import com.nomadics9.ananas.databinding.FragmentSeasonBinding
|
||||
import com.nomadics9.ananas.dialogs.ErrorDialogFragment
|
||||
import com.nomadics9.ananas.dialogs.getStorageSelectionDialog
|
||||
import com.nomadics9.ananas.models.FindroidEpisode
|
||||
import com.nomadics9.ananas.utils.checkIfLoginRequired
|
||||
import com.nomadics9.ananas.viewmodels.SeasonEvent
|
||||
|
@ -23,6 +29,11 @@ import com.nomadics9.ananas.viewmodels.SeasonViewModel
|
|||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.nomadics9.ananas.models.UiText
|
||||
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SeasonFragment : Fragment() {
|
||||
|
||||
|
@ -31,6 +42,7 @@ class SeasonFragment : Fragment() {
|
|||
private val args: SeasonFragmentArgs by navArgs()
|
||||
|
||||
private lateinit var errorDialog: ErrorDialogFragment
|
||||
private lateinit var downloadPreparingDialog: AlertDialog
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
|
@ -38,6 +50,39 @@ class SeasonFragment : Fragment() {
|
|||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
binding = FragmentSeasonBinding.inflate(inflater, container, false)
|
||||
val menuHost: MenuHost = requireActivity()
|
||||
menuHost.addMenuProvider(
|
||||
object : MenuProvider {
|
||||
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
|
||||
menuInflater.inflate(com.nomadics9.ananas.core.R.menu.season_menu, menu)
|
||||
}
|
||||
|
||||
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
|
||||
return when (menuItem.itemId) {
|
||||
com.nomadics9.ananas.core.R.id.action_download_season -> {
|
||||
if (requireContext().getExternalFilesDirs(null).filterNotNull().size > 1) {
|
||||
val storageDialog = getStorageSelectionDialog(
|
||||
requireContext(),
|
||||
onItemSelected = { storageIndex ->
|
||||
createEpisodesToDownloadDialog(storageIndex)
|
||||
},
|
||||
onCancel = {
|
||||
},
|
||||
)
|
||||
viewModel.download()
|
||||
return true
|
||||
}
|
||||
createEpisodesToDownloadDialog()
|
||||
return true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
viewLifecycleOwner,
|
||||
Lifecycle.State.RESUMED,
|
||||
)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
|
@ -57,6 +102,25 @@ class SeasonFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
|
||||
viewModel.downloadStatus.collect { (status, progress) ->
|
||||
when (status) {
|
||||
10 -> {
|
||||
downloadPreparingDialog.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.downloadError.collect { uiText ->
|
||||
createErrorDialog(uiText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
launch {
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
|
@ -110,6 +174,47 @@ class SeasonFragment : Fragment() {
|
|||
checkIfLoginRequired(uiState.error.message)
|
||||
}
|
||||
|
||||
private fun createDownloadPreparingDialog() {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
downloadPreparingDialog = builder
|
||||
.setTitle(com.nomadics9.ananas.core.R.string.preparing_download)
|
||||
.setView(com.nomadics9.ananas.R.layout.preparing_download_dialog)
|
||||
.setCancelable(false)
|
||||
.create()
|
||||
downloadPreparingDialog.show()
|
||||
}
|
||||
|
||||
private fun createErrorDialog(uiText: UiText) {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
builder
|
||||
.setTitle(com.nomadics9.ananas.core.R.string.downloading_error)
|
||||
.setMessage(uiText.asString(requireContext().resources))
|
||||
.setPositiveButton(getString(com.nomadics9.ananas.core.R.string.close)) { _, _ ->
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun createEpisodesToDownloadDialog(storageIndex: Int = 0) {
|
||||
val builder = MaterialAlertDialogBuilder(requireContext())
|
||||
val dialog = builder
|
||||
.setTitle(com.nomadics9.ananas.core.R.string.download_season_dialog_title)
|
||||
.setMessage(com.nomadics9.ananas.core.R.string.download_season_dialog_question)
|
||||
.setPositiveButton(com.nomadics9.ananas.core.R.string.download_season_dialog_download_all) { _, _ ->
|
||||
createDownloadPreparingDialog()
|
||||
viewModel.download(storageIndex = storageIndex, downloadWatched = true)
|
||||
}
|
||||
.setNegativeButton(com.nomadics9.ananas.core.R.string.download_season_dialog_download_unwatched) { _, _ ->
|
||||
createDownloadPreparingDialog()
|
||||
viewModel.download(storageIndex = storageIndex, downloadWatched = false)
|
||||
}
|
||||
.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private fun navigateToEpisodeBottomSheetFragment(episode: FindroidEpisode) {
|
||||
findNavController().navigate(
|
||||
SeasonFragmentDirections.actionSeasonFragmentToEpisodeBottomSheetFragment(
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import org.gradle.api.JavaVersion
|
||||
|
||||
object Versions {
|
||||
const val appCode = 4
|
||||
const val appCode = 5
|
||||
const val appName = "0.14.2"
|
||||
|
||||
const val compileSdk = 34
|
||||
|
|
|
@ -15,15 +15,29 @@ import org.jellyfin.sdk.model.api.ItemFields
|
|||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
import com.nomadics9.ananas.models.UiText
|
||||
import com.nomadics9.ananas.utils.Downloader
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlin.random.Random
|
||||
|
||||
@HiltViewModel
|
||||
class SeasonViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
private val jellyfinRepository: JellyfinRepository,
|
||||
) : ViewModel() {
|
||||
private val downloader: Downloader,
|
||||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
|
||||
private val _downloadStatus = MutableStateFlow(Pair(0, 0))
|
||||
val downloadStatus = _downloadStatus.asStateFlow()
|
||||
|
||||
private val _downloadError = MutableSharedFlow<UiText>()
|
||||
val downloadError = _downloadError.asSharedFlow()
|
||||
|
||||
private val eventsChannel = Channel<SeasonEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
|
@ -51,6 +65,24 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun download(sourceIndex: Int = 0, storageIndex: Int = 0, downloadWatched: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
for (episode in jellyfinRepository.getEpisodes(season.seriesId, season.id)) {
|
||||
val item = jellyfinRepository.getEpisode(episode.id)
|
||||
if (item.played && !downloadWatched) {
|
||||
continue }
|
||||
val result = downloader.downloadItem(item, item.sources[sourceIndex].id, storageIndex)
|
||||
if (result.second != null) {
|
||||
_downloadError.emit(result.second!!)
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
// Send one time signal to fragment that the download has been initiated
|
||||
_downloadStatus.emit(Pair(10, Random.nextInt()))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getSeason(seasonId: UUID): FindroidSeason {
|
||||
return jellyfinRepository.getSeason(seasonId)
|
||||
}
|
||||
|
|
9
core/src/main/res/menu/season_menu.xml
Normal file
9
core/src/main/res/menu/season_menu.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/action_download_season"
|
||||
android:icon="@drawable/ic_download"
|
||||
android:title="@string/download_season_button_description"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
|
@ -195,4 +195,9 @@
|
|||
<string name="alaskarTV_requests">AlaskarTV Requests</string>
|
||||
<string name="pref_player_trickplay_gesture">Trick Play in seek gesture</string>
|
||||
<string name="pref_player_trickplay_gesture_summary">Requires \'Seek gesture\' and \'Trick Play\'</string>
|
||||
<string name="download_season_button_description">Download season</string>
|
||||
<string name="download_season_dialog_title">Download Season</string>
|
||||
<string name="download_season_dialog_question">Which episodes do you want to download?</string>
|
||||
<string name="download_season_dialog_download_all">All Episodes</string>
|
||||
<string name="download_season_dialog_download_unwatched">Unwatched Episodes</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue