0
0
mirror of https://github.com/mediathekview/zapp.git synced 2024-09-20 04:12:14 +02:00

Fill download list

This commit is contained in:
Christine Coenen 2022-11-10 11:41:51 +01:00
parent 83815c9d39
commit d4aca3e089
7 changed files with 218 additions and 3 deletions

View File

@ -64,7 +64,7 @@ class KoinModules {
viewModel { AbstractPlayerActivityViewModel(get()) }
viewModel { ChannelPlayerActivityViewModel(get()) }
viewModel { PersonalViewModel() }
viewModel { PersonalViewModel(get()) }
viewModel { DownloadsViewModel(get(), get()) }
viewModel { ProgramInfoViewModel(androidApplication(), get()) }
viewModel { MediathekListFragmentViewModel(get()) }

View File

@ -5,10 +5,13 @@ import android.view.*
import androidx.core.view.MenuProvider
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ConcatAdapter
import de.christinecoenen.code.zapp.R
import de.christinecoenen.code.zapp.app.personal.adapter.DownloadListAdapter
import de.christinecoenen.code.zapp.app.personal.adapter.HeaderAdapater
import de.christinecoenen.code.zapp.databinding.PersonalFragmentBinding
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
class PersonalFragment : Fragment(), MenuProvider {
@ -17,13 +20,19 @@ class PersonalFragment : Fragment(), MenuProvider {
private val binding: PersonalFragmentBinding get() = _binding!!
private val viewModel: PersonalViewModel by viewModel()
private lateinit var outerAdapter: ConcatAdapter
private lateinit var downloadsAdapter: DownloadListAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
downloadsAdapter = DownloadListAdapter(lifecycleScope)
outerAdapter = ConcatAdapter(
HeaderAdapater(R.string.activity_main_tab_downloads),
downloadsAdapter
)
}
@ -36,6 +45,12 @@ class PersonalFragment : Fragment(), MenuProvider {
binding.list.adapter = outerAdapter
lifecycleScope.launch {
viewModel.downloadsFlow.collect {
downloadsAdapter.setShows(it)
}
}
requireActivity().addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
return binding.root

View File

@ -1,9 +1,11 @@
package de.christinecoenen.code.zapp.app.personal
import androidx.lifecycle.ViewModel
import de.christinecoenen.code.zapp.repositories.MediathekRepository
class PersonalViewModel : ViewModel() {
class PersonalViewModel(mediathekRepository: MediathekRepository) : ViewModel() {
val downloadsFlow = mediathekRepository.getDownloads(3)
}

View File

@ -0,0 +1,127 @@
package de.christinecoenen.code.zapp.app.personal.adapter
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import de.christinecoenen.code.zapp.R
import de.christinecoenen.code.zapp.databinding.MediathekListFragmentItemBinding
import de.christinecoenen.code.zapp.models.shows.DownloadStatus
import de.christinecoenen.code.zapp.models.shows.MediathekShow
import de.christinecoenen.code.zapp.repositories.MediathekRepository
import de.christinecoenen.code.zapp.utils.system.ColorHelper.themeColor
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class DownloadItemViewHolder(
private val binding: MediathekListFragmentItemBinding
) : RecyclerView.ViewHolder(binding.root), KoinComponent {
private val mediathekRepository: MediathekRepository by inject()
private val bgColorDefault = binding.root.context.themeColor(R.attr.backgroundColor)
private val bgColorHighlight by lazy { binding.root.context.themeColor(R.attr.colorSurface) }
private var isRelevantForUserJob: Job? = null
private var downloadProgressJob: Job? = null
private var downloadStatusJob: Job? = null
private var playbackPositionJob: Job? = null
suspend fun setShow(show: MediathekShow) = withContext(Dispatchers.Main) {
binding.root.visibility = View.GONE
isRelevantForUserJob?.cancel()
downloadProgressJob?.cancel()
downloadStatusJob?.cancel()
playbackPositionJob?.cancel()
binding.title.text = show.title
binding.topic.text = show.topic
// fix layout_constraintWidth_max not be applied correctly
binding.topic.requestLayout()
binding.duration.text = show.formattedDuration
binding.channel.text = show.channel
binding.time.text = show.formattedTimestamp
binding.subtitle.isVisible = show.hasSubtitle
binding.subtitleDivider.isVisible = show.hasSubtitle
binding.downloadProgress.isVisible = false
binding.downloadProgressIcon.isVisible = false
binding.downloadStatusIcon.isVisible = false
binding.viewingStatus.isVisible = false
binding.viewingProgress.isVisible = false
binding.root.setBackgroundColor(bgColorDefault)
binding.root.visibility = View.VISIBLE
isRelevantForUserJob = launch { getIsRelevantForUserFlow(show) }
downloadProgressJob = launch { updateDownloadProgressFlow(show) }
downloadStatusJob = launch { updateDownloadStatusFlow(show) }
playbackPositionJob = launch { updatePlaybackPositionPercent(show) }
}
private suspend fun getIsRelevantForUserFlow(show: MediathekShow) {
mediathekRepository
.getIsRelevantForUser(show.apiId)
.collectLatest(::updateIsRelevantForUser)
}
private suspend fun updateDownloadProgressFlow(show: MediathekShow) {
mediathekRepository
.getDownloadProgress(show.apiId)
.collectLatest(::updateDownloadProgress)
}
private suspend fun updateDownloadStatusFlow(show: MediathekShow) {
mediathekRepository
.getDownloadStatus(show.apiId)
.collectLatest(::updateDownloadStatus)
}
private suspend fun updatePlaybackPositionPercent(show: MediathekShow) {
mediathekRepository
.getPlaybackPositionPercent(show.apiId)
.collectLatest(::updatePlaybackPositionPercent)
}
private fun updateIsRelevantForUser(isRelevant: Boolean) {
binding.root.setBackgroundColor(if (isRelevant) bgColorHighlight else bgColorDefault)
}
private fun updateDownloadProgress(progress: Int) {
binding.downloadProgress.progress = progress
}
private fun updateDownloadStatus(status: DownloadStatus) {
binding.downloadStatusIcon.isVisible = status == DownloadStatus.FAILED ||
status == DownloadStatus.COMPLETED
binding.downloadStatusIcon.setImageResource(
when (status) {
DownloadStatus.COMPLETED -> R.drawable.ic_baseline_save_alt_24
DownloadStatus.FAILED -> R.drawable.ic_outline_warning_amber_24
else -> 0
}
)
binding.downloadProgress.isVisible = status == DownloadStatus.QUEUED ||
status == DownloadStatus.DOWNLOADING ||
status == DownloadStatus.PAUSED ||
status == DownloadStatus.ADDED
binding.downloadProgressIcon.isVisible = binding.downloadProgress.isVisible
binding.downloadProgress.isIndeterminate = status != DownloadStatus.DOWNLOADING
}
private fun updatePlaybackPositionPercent(percent: Float) {
binding.viewingStatus.isVisible = percent > 0
binding.viewingProgress.progress = (percent * binding.viewingProgress.max).toInt()
binding.viewingProgress.isVisible = percent > 0
}
}

View File

@ -0,0 +1,58 @@
package de.christinecoenen.code.zapp.app.personal.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.recyclerview.widget.RecyclerView
import de.christinecoenen.code.zapp.databinding.MediathekListFragmentItemBinding
import de.christinecoenen.code.zapp.models.shows.PersistedMediathekShow
import kotlinx.coroutines.launch
// TODO: use another, more spacialized viewholder
class DownloadListAdapter(
private val scope: LifecycleCoroutineScope,
private val listener: Listener? = null
) : RecyclerView.Adapter<DownloadItemViewHolder>() {
private var persistedShows = mutableListOf<PersistedMediathekShow>()
public fun setShows(shows: List<PersistedMediathekShow>) {
persistedShows = shows.toMutableList()
// TODO: use diff util
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadItemViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = MediathekListFragmentItemBinding.inflate(layoutInflater, parent, false)
val holder = DownloadItemViewHolder(binding)
binding.root.setOnClickListener {
listener?.onShowClicked(persistedShows[holder.bindingAdapterPosition])
}
binding.root.setOnLongClickListener {
listener?.onShowLongClicked(
persistedShows[holder.bindingAdapterPosition],
binding.root
)
true
}
return holder
}
override fun onBindViewHolder(holder: DownloadItemViewHolder, position: Int) {
scope.launch {
holder.setShow(persistedShows[holder.bindingAdapterPosition].mediathekShow)
}
}
override fun getItemCount() = persistedShows.size
interface Listener {
fun onShowClicked(show: PersistedMediathekShow)
fun onShowLongClicked(show: PersistedMediathekShow, view: View)
}
}

View File

@ -17,6 +17,9 @@ interface MediathekShowDao {
@Query("SELECT * FROM PersistedMediathekShow WHERE (downloadStatus IN (1,2,3,4,6,9)) AND (topic LIKE :searchQuery OR title LIKE :searchQuery) ORDER BY downloadedAt DESC")
fun getAllDownloads(searchQuery: String): PagingSource<Int, PersistedMediathekShow>
@Query("SELECT * FROM PersistedMediathekShow WHERE (downloadStatus IN (1,2,3,4,6,9)) ORDER BY downloadedAt DESC LIMIT :limit")
fun getDownloads(limit: Int): Flow<List<PersistedMediathekShow>>
@Query("SELECT * FROM PersistedMediathekShow WHERE id=:id")
fun getFromId(id: Int): Flow<PersistedMediathekShow>

View File

@ -12,8 +12,18 @@ import org.joda.time.DateTime
class MediathekRepository(private val database: Database) {
fun getDownloads(limit: Int): Flow<List<PersistedMediathekShow>> {
return database
.mediathekShowDao()
.getDownloads(limit)
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
}
fun getDownloads(searchQuery: String): PagingSource<Int, PersistedMediathekShow> {
return database.mediathekShowDao().getAllDownloads("%$searchQuery%")
return database
.mediathekShowDao()
.getAllDownloads("%$searchQuery%")
}
suspend fun persistOrUpdateShow(show: MediathekShow): Flow<PersistedMediathekShow> =