feat: add badge for downloaded videos

This commit is contained in:
Bnyro 2025-01-09 15:48:43 +01:00
parent 0115e4c070
commit 8a3ea3b695
7 changed files with 86 additions and 10 deletions

View File

@ -23,6 +23,9 @@ interface DownloadDao {
@Query("SELECT * FROM download WHERE videoId = :videoId") @Query("SELECT * FROM download WHERE videoId = :videoId")
suspend fun findById(videoId: String): DownloadWithItems? suspend fun findById(videoId: String): DownloadWithItems?
@Query("SELECT EXISTS (SELECT * FROM download WHERE videoId = :videoId)")
suspend fun exists(videoId: String): Boolean
@Query("SELECT videoId FROM downloadItem WHERE type = :fileType ORDER BY RANDOM() LIMIT 1") @Query("SELECT videoId FROM downloadItem WHERE type = :fileType ORDER BY RANDOM() LIMIT 1")
suspend fun getRandomVideoIdByFileType(fileType: FileType): String? suspend fun getRandomVideoIdByFileType(fileType: FileType): String?

View File

@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.R import com.github.libretube.R
@ -15,6 +16,7 @@ import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.obj.StreamItem import com.github.libretube.api.obj.StreamItem
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.PlaylistType import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.TAG import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.dpToPx import com.github.libretube.extensions.dpToPx
@ -118,6 +120,15 @@ class PlaylistAdapter(
} }
streamItem.duration?.let { watchProgress.setWatchProgressLength(videoId, it) } streamItem.duration?.let { watchProgress.setWatchProgressLength(videoId, it) }
CoroutineScope(Dispatchers.IO).launch {
val isDownloaded =
DatabaseHolder.Database.downloadDao().exists(videoId)
withContext(Dispatchers.Main) {
downloadBadge.isVisible = isDownloaded
}
}
} }
} }

View File

@ -3,6 +3,7 @@ package com.github.libretube.ui.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.paging.PagingDataAdapter import androidx.paging.PagingDataAdapter
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.api.JsonHelper import com.github.libretube.api.JsonHelper
@ -12,6 +13,7 @@ import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.ChannelRowBinding import com.github.libretube.databinding.ChannelRowBinding
import com.github.libretube.databinding.PlaylistsRowBinding import com.github.libretube.databinding.PlaylistsRowBinding
import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.enums.PlaylistType import com.github.libretube.enums.PlaylistType
import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.formatShort
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
@ -27,6 +29,10 @@ import com.github.libretube.ui.sheets.PlaylistOptionsBottomSheet
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.SearchViewHolder import com.github.libretube.ui.viewholders.SearchViewHolder
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
class SearchResultsAdapter( class SearchResultsAdapter(
@ -112,6 +118,15 @@ class SearchResultsAdapter(
NavigationHelper.navigateChannel(root.context, item.uploaderUrl) NavigationHelper.navigateChannel(root.context, item.uploaderUrl)
} }
watchProgress.setWatchProgressLength(videoId, item.duration) watchProgress.setWatchProgressLength(videoId, item.duration)
CoroutineScope(Dispatchers.IO).launch {
val isDownloaded =
DatabaseHolder.Database.downloadDao().exists(videoId)
withContext(Dispatchers.Main) {
downloadBadge.isVisible = isDownloaded
}
}
} }
} }

View File

@ -2,11 +2,11 @@ package com.github.libretube.ui.adapters
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -17,6 +17,7 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.AllCaughtUpRowBinding import com.github.libretube.databinding.AllCaughtUpRowBinding
import com.github.libretube.databinding.TrendingRowBinding import com.github.libretube.databinding.TrendingRowBinding
import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.databinding.VideoRowBinding
import com.github.libretube.db.DatabaseHolder
import com.github.libretube.extensions.ceilHalf import com.github.libretube.extensions.ceilHalf
import com.github.libretube.extensions.dpToPx import com.github.libretube.extensions.dpToPx
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
@ -29,6 +30,10 @@ import com.github.libretube.ui.extensions.setWatchProgressLength
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.VideosViewHolder import com.github.libretube.ui.viewholders.VideosViewHolder
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class VideosAdapter( class VideosAdapter(
private val streamItems: MutableList<StreamItem>, private val streamItems: MutableList<StreamItem>,
@ -86,12 +91,10 @@ class VideosAdapter(
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
override fun onBindViewHolder(holder: VideosViewHolder, position: Int) { override fun onBindViewHolder(holder: VideosViewHolder, position: Int) {
val video = streamItems[position] val video = streamItems[position]
val videoId = video.url?.toID() val videoId = video.url.orEmpty().toID()
videoId?.let {
(holder.trendingRowBinding?.watchProgress ?: holder.videoRowBinding!!.watchProgress) (holder.trendingRowBinding?.watchProgress ?: holder.videoRowBinding!!.watchProgress)
.setWatchProgressLength(it, video.duration ?: 0L) .setWatchProgressLength(videoId, video.duration ?: 0L)
}
val context = ( val context = (
holder.videoRowBinding ?: holder.trendingRowBinding holder.videoRowBinding ?: holder.trendingRowBinding
@ -119,7 +122,7 @@ class VideosAdapter(
ImageHelper.loadImage(video.thumbnail, thumbnail) ImageHelper.loadImage(video.thumbnail, thumbnail)
ImageHelper.loadImage(video.uploaderAvatar, channelImage, true) ImageHelper.loadImage(video.uploaderAvatar, channelImage, true)
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, video.url) NavigationHelper.navigateVideo(root.context, videoId)
} }
root.setOnLongClickListener { root.setOnLongClickListener {
@ -156,7 +159,7 @@ class VideosAdapter(
} }
root.setOnClickListener { root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, video.url) NavigationHelper.navigateVideo(root.context, videoId)
} }
root.setOnLongClickListener { root.setOnLongClickListener {
@ -171,6 +174,15 @@ class VideosAdapter(
sheet.show(fragmentManager, VideosAdapter::class.java.name) sheet.show(fragmentManager, VideosAdapter::class.java.name)
true true
} }
CoroutineScope(Dispatchers.IO).launch {
val isDownloaded =
DatabaseHolder.Database.downloadDao().exists(videoId)
withContext(Dispatchers.Main) {
downloadBadge.isVisible = isDownloaded
}
}
} }
} }

View File

@ -4,6 +4,7 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.constants.IntentData import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.VideoRowBinding import com.github.libretube.databinding.VideoRowBinding
@ -17,8 +18,11 @@ import com.github.libretube.ui.extensions.setWatchProgressLength
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
import com.github.libretube.ui.viewholders.WatchHistoryViewHolder import com.github.libretube.ui.viewholders.WatchHistoryViewHolder
import com.github.libretube.util.TextUtils import com.github.libretube.util.TextUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class WatchHistoryAdapter( class WatchHistoryAdapter(
private val watchHistory: MutableList<WatchHistoryItem> private val watchHistory: MutableList<WatchHistoryItem>
@ -98,6 +102,15 @@ class WatchHistoryAdapter(
video.videoId, video.videoId,
video.duration video.duration
) )
CoroutineScope(Dispatchers.IO).launch {
val isDownloaded =
DatabaseHolder.Database.downloadDao().exists(video.videoId)
withContext(Dispatchers.Main) {
downloadBadge.isVisible = isDownloaded
}
}
} }
} }
} }

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?colorControlNormal"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#FF000000"
android:pathData="m434,663 l226,-227 -56,-56 -170,170 -85,-85 -57,57 142,141ZM160,800q-33,0 -56.5,-23.5T80,720v-480q0,-33 23.5,-56.5T160,160h240l80,80h320q33,0 56.5,23.5T880,320v400q0,33 -23.5,56.5T800,800L160,800ZM160,720h640v-400L447,320l-80,-80L160,240v480ZM160,720v-480,480Z" />
</vector>

View File

@ -107,7 +107,7 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="2" android:maxLines="2"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/download_badge"
app:layout_constraintStart_toEndOf="@id/thumbnail_card" app:layout_constraintStart_toEndOf="@id/thumbnail_card"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
@ -119,10 +119,22 @@
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textAlignment="viewStart" android:textAlignment="viewStart"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/download_badge"
app:layout_constraintStart_toEndOf="@id/thumbnail_card" app:layout_constraintStart_toEndOf="@id/thumbnail_card"
app:layout_constraintTop_toBottomOf="@id/video_title" /> app:layout_constraintTop_toBottomOf="@id/video_title" />
<ImageView
android:id="@+id/download_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_download_folder"
app:layout_constraintEnd_toEndOf="parent"
android:paddingStart="6dp"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlSymmetry"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout <LinearLayout
android:id="@+id/channel_container" android:id="@+id/channel_container"
android:layout_width="0dp" android:layout_width="0dp"