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:
parent
83815c9d39
commit
d4aca3e089
@ -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()) }
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
||||
|
@ -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> =
|
||||
|
Loading…
Reference in New Issue
Block a user