Merge AGP_8.0 with upstream and fix some deprecations

This commit is contained in:
Bnyro 2023-05-10 19:39:37 +02:00
commit cd12dbd7db
299 changed files with 2232 additions and 4401 deletions

7
.github/uploader.py vendored
View File

@ -1,6 +1,11 @@
from os import system as run, listdir, remove from os import system as run, listdir, remove
from json import load
import tgconfig import tgconfig
with open("../.github/commit.json") as f:
data = load(f)
message = f"Commit {data['sha'][0:7]}, signed off by: {data['commit']['author']['name']}"
files, signed_files, unsigned_files = listdir(), [], [] files, signed_files, unsigned_files = listdir(), [], []
for file in files: for file in files:
@ -15,7 +20,7 @@ if len(signed_files):
if tgconfig.GH_REPO.lower() == "libre-tube/libretube": if tgconfig.GH_REPO.lower() == "libre-tube/libretube":
run("git add -f *") run("git add -f *")
run('git commit -m "WORKFLOW: ALPHA BUILDS"') run(f"git commit -m \"{message}\"")
run("git push -u") run("git push -u")
else: else:
print("Official Repo not Detected") print("Official Repo not Detected")

View File

@ -42,10 +42,10 @@ jobs:
- name: Git Configuraion - name: Git Configuraion
run: | run: |
git config --global user.name "alefvanoon" git config --global user.name "Bnyro"
git config --global user.email "53198048+alefvanoon@users.noreply.github.com" git config --global user.email "bnyro@tutanota.com"
git config --global credential.helper store git config --global credential.helper store
echo "https://alefvanoon:${{ secrets.GH_TOKEN }}@github.com" > ~/.git-credentials echo "https://bnyro:${{ secrets.GH_TOKEN }}@github.com" > ~/.git-credentials
- name: Compile - name: Compile
run: | run: |
@ -68,16 +68,16 @@ jobs:
run: | run: |
mv .github/uploader.py . mv .github/uploader.py .
echo "GH_REPO = '${{ github.repository }}'" > tgconfig.py echo "GH_REPO = '${{ github.repository }}'" > tgconfig.py
git clone --filter=blob:none https://github.com/LibreTubeAlpha/Archive archive git clone --filter=blob:none https://github.com/libre-tube/NightlyBuilds nightly
rm -rf archive/*.apk rm -rf nightly/*.apk
mv app/build/outputs/apk/debug/*.apk archive/ mv app/build/outputs/apk/debug/*.apk nightly/
cd archive cd nightly
python ../uploader.py python ../uploader.py
- name: Telegram Bot - name: Telegram Bot
continue-on-error: true continue-on-error: true
run: | run: |
cd archive cd nightly
mv ../tgconfig.py . mv ../tgconfig.py .
echo "TG_TOKEN = '${{ secrets.TG_TOKEN }}'" >> tgconfig.py echo "TG_TOKEN = '${{ secrets.TG_TOKEN }}'" >> tgconfig.py
echo "TG_API_ID = '${{ secrets.TG_API_ID }}'" >> tgconfig.py echo "TG_API_ID = '${{ secrets.TG_API_ID }}'" >> tgconfig.py
@ -92,5 +92,5 @@ jobs:
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: app name: app
path: archive/*.apk path: nightly/*.apk

View File

@ -11,7 +11,7 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: ktlint - name: ktlint
uses: ScaCap/action-ktlint@v1.6 uses: ScaCap/action-ktlint@v1.7
with: with:
github_token: ${{ secrets.GITHUB_TOKEN }} github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-check reporter: github-pr-check

View File

@ -1,12 +1,17 @@
<div align="center"> <div align="center">
<img src="https://libre-tube.github.io/images/gh-banner.png" width="auto" height="auto" alt="LibreTube"> <img src="https://libre-tube.github.io/images/gh-banner.png" width="auto" height="auto" alt="LibreTube">
[![GPL-v3](https://libre-tube.github.io/assets/widgets/license-widget.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![GPL-v3](https://libre-tube.github.io/assets/widgets/license-widget.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html)
</div><div align="center" style="width:100%; display:flex; justify-content:space-between;">
[![Matrix](https://libre-tube.github.io/assets/widgets/mat-widget.svg)](https://matrix.to/#/#LibreTube:matrix.org) [![Matrix](https://libre-tube.github.io/assets/widgets/mat-widget.svg)](https://matrix.to/#/#LibreTube:matrix.org)
[![Mastodon](https://libre-tube.github.io/assets/widgets/mast-widget.svg)](https://fosstodon.org/@libretube) [![Mastodon](https://libre-tube.github.io/assets/widgets/mast-widget.svg)](https://fosstodon.org/@libretube)
[![Telegram](https://libre-tube.github.io/assets/widgets/tg-widget.svg)](https://t.me/libretube) [![Telegram](https://libre-tube.github.io/assets/widgets/tg-widget.svg)](https://t.me/libretube)
[![Reddit](https://libre-tube.github.io/assets/widgets/rd-widget.svg)](https://www.reddit.com/r/Libretube/)
[![Discord](https://libre-tube.github.io/assets/widgets/discord-widget.svg)](https://discord.gg/Qc34xCj2GV) </div>
> **Note** <br>
> We don't accept feature or bug requests on these platforms. Kindly submit requests only on GitHub.
</div><div align="center" style="width:100%; display:flex; justify-content:space-between;"> </div><div align="center" style="width:100%; display:flex; justify-content:space-between;">

View File

@ -14,8 +14,8 @@ android {
applicationId 'com.github.libretube' applicationId 'com.github.libretube'
minSdk 21 minSdk 21
targetSdk 33 targetSdk 33
versionCode 33 versionCode 34
versionName '0.14.0' versionName '0.14.1'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
resValue "string", "app_name", "LibreTube" resValue "string", "app_name", "LibreTube"

View File

@ -49,7 +49,7 @@ class LibreTubeApp : Application() {
*/ */
NotificationHelper.enqueueWork( NotificationHelper.enqueueWork(
context = this, context = this,
existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP existingPeriodicWorkPolicy = ExistingPeriodicWorkPolicy.KEEP,
) )
/** /**
@ -76,21 +76,21 @@ class LibreTubeApp : Application() {
private fun initializeNotificationChannels() { private fun initializeNotificationChannels() {
val downloadChannel = NotificationChannelCompat.Builder( val downloadChannel = NotificationChannelCompat.Builder(
DOWNLOAD_CHANNEL_ID, DOWNLOAD_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_LOW NotificationManagerCompat.IMPORTANCE_LOW,
) )
.setName(getString(R.string.download_channel_name)) .setName(getString(R.string.download_channel_name))
.setDescription(getString(R.string.download_channel_description)) .setDescription(getString(R.string.download_channel_description))
.build() .build()
val backgroundChannel = NotificationChannelCompat.Builder( val backgroundChannel = NotificationChannelCompat.Builder(
BACKGROUND_CHANNEL_ID, BACKGROUND_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_LOW NotificationManagerCompat.IMPORTANCE_LOW,
) )
.setName(getString(R.string.background_channel_name)) .setName(getString(R.string.background_channel_name))
.setDescription(getString(R.string.background_channel_description)) .setDescription(getString(R.string.background_channel_description))
.build() .build()
val pushChannel = NotificationChannelCompat.Builder( val pushChannel = NotificationChannelCompat.Builder(
PUSH_CHANNEL_ID, PUSH_CHANNEL_ID,
NotificationManagerCompat.IMPORTANCE_DEFAULT NotificationManagerCompat.IMPORTANCE_DEFAULT,
) )
.setName(getString(R.string.push_channel_name)) .setName(getString(R.string.push_channel_name))
.setDescription(getString(R.string.push_channel_description)) .setDescription(getString(R.string.push_channel_description))
@ -101,8 +101,8 @@ class LibreTubeApp : Application() {
listOf( listOf(
downloadChannel, downloadChannel,
backgroundChannel, backgroundChannel,
pushChannel pushChannel,
) ),
) )
} }

View File

@ -41,26 +41,26 @@ interface PipedApi {
@GET("sponsors/{videoId}") @GET("sponsors/{videoId}")
suspend fun getSegments( suspend fun getSegments(
@Path("videoId") videoId: String, @Path("videoId") videoId: String,
@Query("category") category: String @Query("category") category: String,
): SegmentData ): SegmentData
@GET("nextpage/comments/{videoId}") @GET("nextpage/comments/{videoId}")
suspend fun getCommentsNextPage( suspend fun getCommentsNextPage(
@Path("videoId") videoId: String, @Path("videoId") videoId: String,
@Query("nextpage") nextPage: String @Query("nextpage") nextPage: String,
): CommentsPage ): CommentsPage
@GET("search") @GET("search")
suspend fun getSearchResults( suspend fun getSearchResults(
@Query("q") searchQuery: String, @Query("q") searchQuery: String,
@Query("filter") filter: String @Query("filter") filter: String,
): SearchResult ): SearchResult
@GET("nextpage/search") @GET("nextpage/search")
suspend fun getSearchResultsNextPage( suspend fun getSearchResultsNextPage(
@Query("q") searchQuery: String, @Query("q") searchQuery: String,
@Query("filter") filter: String, @Query("filter") filter: String,
@Query("nextpage") nextPage: String @Query("nextpage") nextPage: String,
): SearchResult ): SearchResult
@GET("suggestions") @GET("suggestions")
@ -72,7 +72,7 @@ interface PipedApi {
@GET("channels/tabs") @GET("channels/tabs")
suspend fun getChannelTab( suspend fun getChannelTab(
@Query("data") data: String, @Query("data") data: String,
@Query("nextpage") nextPage: String? = null @Query("nextpage") nextPage: String? = null,
): ChannelTabResponse ): ChannelTabResponse
@GET("user/{name}") @GET("user/{name}")
@ -81,7 +81,7 @@ interface PipedApi {
@GET("nextpage/channel/{channelId}") @GET("nextpage/channel/{channelId}")
suspend fun getChannelNextPage( suspend fun getChannelNextPage(
@Path("channelId") channelId: String, @Path("channelId") channelId: String,
@Query("nextpage") nextPage: String @Query("nextpage") nextPage: String,
): Channel ): Channel
@GET("playlists/{playlistId}") @GET("playlists/{playlistId}")
@ -90,7 +90,7 @@ interface PipedApi {
@GET("nextpage/playlists/{playlistId}") @GET("nextpage/playlists/{playlistId}")
suspend fun getPlaylistNextPage( suspend fun getPlaylistNextPage(
@Path("playlistId") playlistId: String, @Path("playlistId") playlistId: String,
@Query("nextpage") nextPage: String @Query("nextpage") nextPage: String,
): Playlist ): Playlist
@POST("login") @POST("login")
@ -102,7 +102,7 @@ interface PipedApi {
@POST("user/delete") @POST("user/delete")
suspend fun deleteAccount( suspend fun deleteAccount(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body password: DeleteUserRequest @Body password: DeleteUserRequest,
) )
@GET("feed") @GET("feed")
@ -110,18 +110,18 @@ interface PipedApi {
@GET("feed/unauthenticated") @GET("feed/unauthenticated")
suspend fun getUnauthenticatedFeed( suspend fun getUnauthenticatedFeed(
@Query("channels") channels: String @Query("channels") channels: String,
): List<StreamItem> ): List<StreamItem>
@POST("feed/unauthenticated") @POST("feed/unauthenticated")
suspend fun getUnauthenticatedFeed( suspend fun getUnauthenticatedFeed(
@Body channels: List<String> @Body channels: List<String>,
): List<StreamItem> ): List<StreamItem>
@GET("subscribed") @GET("subscribed")
suspend fun isSubscribed( suspend fun isSubscribed(
@Query("channelId") channelId: String, @Query("channelId") channelId: String,
@Header("Authorization") token: String @Header("Authorization") token: String,
): Subscribed ): Subscribed
@GET("subscriptions") @GET("subscriptions")
@ -129,37 +129,37 @@ interface PipedApi {
@GET("subscriptions/unauthenticated") @GET("subscriptions/unauthenticated")
suspend fun unauthenticatedSubscriptions( suspend fun unauthenticatedSubscriptions(
@Query("channels") channels: String @Query("channels") channels: String,
): List<Subscription> ): List<Subscription>
@POST("subscriptions/unauthenticated") @POST("subscriptions/unauthenticated")
suspend fun unauthenticatedSubscriptions( suspend fun unauthenticatedSubscriptions(
@Body channels: List<String> @Body channels: List<String>,
): List<Subscription> ): List<Subscription>
@POST("subscribe") @POST("subscribe")
suspend fun subscribe( suspend fun subscribe(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body subscribe: Subscribe @Body subscribe: Subscribe,
): Message ): Message
@POST("unsubscribe") @POST("unsubscribe")
suspend fun unsubscribe( suspend fun unsubscribe(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body subscribe: Subscribe @Body subscribe: Subscribe,
): Message ): Message
@POST("import") @POST("import")
suspend fun importSubscriptions( suspend fun importSubscriptions(
@Query("override") override: Boolean, @Query("override") override: Boolean,
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body channels: List<String> @Body channels: List<String>,
): Message ): Message
@POST("import/playlist") @POST("import/playlist")
suspend fun clonePlaylist( suspend fun clonePlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body playlistId: PlaylistId @Body playlistId: PlaylistId,
): PlaylistId ): PlaylistId
@GET("user/playlists") @GET("user/playlists")
@ -168,30 +168,30 @@ interface PipedApi {
@POST("user/playlists/rename") @POST("user/playlists/rename")
suspend fun renamePlaylist( suspend fun renamePlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body playlistId: PlaylistId @Body playlistId: PlaylistId,
): Message ): Message
@POST("user/playlists/delete") @POST("user/playlists/delete")
suspend fun deletePlaylist( suspend fun deletePlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body playlistId: PlaylistId @Body playlistId: PlaylistId,
): Message ): Message
@POST("user/playlists/create") @POST("user/playlists/create")
suspend fun createPlaylist( suspend fun createPlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body name: Playlists @Body name: Playlists,
): PlaylistId ): PlaylistId
@POST("user/playlists/add") @POST("user/playlists/add")
suspend fun addToPlaylist( suspend fun addToPlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body playlistId: PlaylistId @Body playlistId: PlaylistId,
): Message ): Message
@POST("user/playlists/remove") @POST("user/playlists/remove")
suspend fun removeFromPlaylist( suspend fun removeFromPlaylist(
@Header("Authorization") token: String, @Header("Authorization") token: String,
@Body playlistId: PlaylistId @Body playlistId: PlaylistId,
): Message ): Message
} }

View File

@ -1,6 +1,5 @@
package com.github.libretube.api package com.github.libretube.api
import android.content.Context
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import com.github.libretube.api.obj.Playlist import com.github.libretube.api.obj.Playlist
import com.github.libretube.api.obj.PlaylistId import com.github.libretube.api.obj.PlaylistId
@ -37,7 +36,7 @@ object PlaylistsHelper {
id = it.playlist.id.toString(), id = it.playlist.id.toString(),
name = it.playlist.name, name = it.playlist.name,
thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl), thumbnail = ProxyHelper.rewriteUrl(it.playlist.thumbnailUrl),
videos = it.videos.size.toLong() videos = it.videos.size.toLong(),
) )
} }
} }
@ -55,7 +54,7 @@ object PlaylistsHelper {
name = relation.playlist.name, name = relation.playlist.name,
thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl), thumbnailUrl = ProxyHelper.rewriteUrl(relation.playlist.thumbnailUrl),
videos = relation.videos.size, videos = relation.videos.size,
relatedStreams = relation.videos.map { it.toStreamItem() } relatedStreams = relation.videos.map { it.toStreamItem() },
) )
} }
} }
@ -118,7 +117,7 @@ object PlaylistsHelper {
val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll() val transaction = DatabaseHolder.Database.localPlaylistsDao().getAll()
.first { it.playlist.id.toString() == playlistId } .first { it.playlist.id.toString() == playlistId }
DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo( DatabaseHolder.Database.localPlaylistsDao().removePlaylistVideo(
transaction.videos[index] transaction.videos[index],
) )
// set a new playlist thumbnail if the first video got removed // set a new playlist thumbnail if the first video got removed
if (index == 0) { if (index == 0) {
@ -129,7 +128,7 @@ object PlaylistsHelper {
} else { } else {
RetrofitInstance.authApi.removeFromPlaylist( RetrofitInstance.authApi.removeFromPlaylist(
PreferenceHelper.getToken(), PreferenceHelper.getToken(),
PlaylistId(playlistId = playlistId, index = index) PlaylistId(playlistId = playlistId, index = index),
).message == "ok" ).message == "ok"
} }
} }
@ -145,23 +144,24 @@ object PlaylistsHelper {
playlistId, playlistId,
*playlist.videos.map { *playlist.videos.map {
StreamItem(url = it) StreamItem(url = it)
}.toTypedArray() }.toTypedArray(),
) )
} else { } else {
// if not logged in, all video information needs to become fetched manually // if not logged in, all video information needs to become fetched manually
runCatching { // Only do so with 20 videos at once to prevent performance issues
val streamItems = playlist.videos.map { playlist.videos.mapIndexed { index, id -> id to index }
async { .groupBy { it.second % 20 }.forEach { (_, videos) ->
runCatching { videos.map {
RetrofitInstance.api.getStreams(it).toStreamItem(it) async {
}.getOrNull() runCatching {
} val stream = RetrofitInstance.api.getStreams(it.first).toStreamItem(
it.first,
)
addToPlaylist(playlistId, stream)
}
}
}.awaitAll()
} }
.awaitAll()
.filterNotNull()
addToPlaylist(playlistId, *streamItems.toTypedArray())
}
} }
} }
}.awaitAll() }.awaitAll()
@ -179,8 +179,7 @@ object PlaylistsHelper {
} }
} }
suspend fun clonePlaylist(context: Context, playlistId: String): String? { suspend fun clonePlaylist(playlistId: String): String? {
val appContext = context.applicationContext
if (!loggedIn) { if (!loggedIn) {
val playlist = RetrofitInstance.api.getPlaylist(playlistId) val playlist = RetrofitInstance.api.getPlaylist(playlistId)
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null val newPlaylist = createPlaylist(playlist.name ?: "Unknown name") ?: return null
@ -211,7 +210,7 @@ object PlaylistsHelper {
return runCatching { return runCatching {
RetrofitInstance.authApi.deletePlaylist( RetrofitInstance.authApi.deletePlaylist(
PreferenceHelper.getToken(), PreferenceHelper.getToken(),
PlaylistId(playlistId) PlaylistId(playlistId),
).message == "ok" ).message == "ok"
}.getOrDefault(false) }.getOrDefault(false)
} }

View File

@ -46,8 +46,7 @@ class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()
} }
} }
fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: () -> PROPTYPE): fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: () -> PROPTYPE): ResettableLazy<PROPTYPE> {
ResettableLazy<PROPTYPE> {
return ResettableLazy(manager, init) return ResettableLazy(manager, init)
} }

View File

@ -53,12 +53,12 @@ object RetrofitInstance {
if ( if (
PreferenceHelper.getBoolean( PreferenceHelper.getBoolean(
PreferenceKeys.AUTH_INSTANCE_TOGGLE, PreferenceKeys.AUTH_INSTANCE_TOGGLE,
false false,
) )
) { ) {
PreferenceHelper.getString( PreferenceHelper.getString(
PreferenceKeys.AUTH_INSTANCE, PreferenceKeys.AUTH_INSTANCE,
PIPED_API_URL PIPED_API_URL,
) )
} else { } else {
url url

View File

@ -53,7 +53,7 @@ object SubscriptionHelper {
context: Context, context: Context,
channelId: String, channelId: String,
channelName: String?, channelName: String?,
onUnsubscribe: () -> Unit onUnsubscribe: () -> Unit,
) { ) {
if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) { if (!PreferenceHelper.getBoolean(PreferenceKeys.CONFIRM_UNSUBSCRIBE, false)) {
runBlocking { runBlocking {
@ -112,10 +112,10 @@ object SubscriptionHelper {
val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId } val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId }
when { when {
subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.unauthenticatedSubscriptions( subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.unauthenticatedSubscriptions(
subscriptions subscriptions,
) )
else -> RetrofitInstance.authApi.unauthenticatedSubscriptions( else -> RetrofitInstance.authApi.unauthenticatedSubscriptions(
subscriptions.joinToString(",") subscriptions.joinToString(","),
) )
} }
} }
@ -129,10 +129,10 @@ object SubscriptionHelper {
val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId } val subscriptions = Database.localSubscriptionDao().getAll().map { it.channelId }
when { when {
subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.getUnauthenticatedFeed( subscriptions.size > GET_SUBSCRIPTIONS_LIMIT -> RetrofitInstance.authApi.getUnauthenticatedFeed(
subscriptions subscriptions,
) )
else -> RetrofitInstance.authApi.getUnauthenticatedFeed( else -> RetrofitInstance.authApi.getUnauthenticatedFeed(
subscriptions.joinToString(",") subscriptions.joinToString(","),
) )
} }
} }

View File

@ -13,5 +13,5 @@ data class Channel(
val subscriberCount: Long = 0, val subscriberCount: Long = 0,
val verified: Boolean = false, val verified: Boolean = false,
val relatedStreams: List<StreamItem> = emptyList(), val relatedStreams: List<StreamItem> = emptyList(),
val tabs: List<ChannelTab> = emptyList() val tabs: List<ChannelTab> = emptyList(),
) )

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class ChannelTab( data class ChannelTab(
val name: String, val name: String,
val data: String val data: String,
) )

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class ChannelTabResponse( data class ChannelTabResponse(
val content: List<ContentItem> = emptyList(), val content: List<ContentItem> = emptyList(),
val nextpage: String? = null val nextpage: String? = null,
) )

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class ChapterSegment( data class ChapterSegment(
val title: String, val title: String,
val image: String, val image: String,
val start: Long val start: Long,
) )

View File

@ -15,5 +15,5 @@ data class Comment(
val pinned: Boolean, val pinned: Boolean,
val thumbnail: String, val thumbnail: String,
val verified: Boolean, val verified: Boolean,
val replyCount: Long val replyCount: Long,
) )

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class CommentsPage( data class CommentsPage(
var comments: List<Comment> = emptyList(), var comments: List<Comment> = emptyList(),
val disabled: Boolean = false, val disabled: Boolean = false,
val nextpage: String? = null val nextpage: String? = null,
) )

View File

@ -24,5 +24,5 @@ data class ContentItem(
val description: String? = null, val description: String? = null,
val subscribers: Long = -1, val subscribers: Long = -1,
val videos: Long = -1, val videos: Long = -1,
val verified: Boolean? = null val verified: Boolean? = null,
) )

View File

@ -12,5 +12,5 @@ data class Instances(
@SerialName("up_to_date") val upToDate: Boolean = true, @SerialName("up_to_date") val upToDate: Boolean = true,
val cdn: Boolean = false, val cdn: Boolean = false,
val registered: Long = 0, val registered: Long = 0,
@SerialName("last_checked") val lastChecked: Long = 0 @SerialName("last_checked") val lastChecked: Long = 0,
) )

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Login( data class Login(
val username: String, val username: String,
val password: String val password: String,
) )

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Message( data class Message(
val error: String? = null, val error: String? = null,
val message: String? = null val message: String? = null,
) )

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class PipedConfig( data class PipedConfig(
val donationUrl: String? = null, val donationUrl: String? = null,
val statusPageUrl: String? = null, val statusPageUrl: String? = null,
val imageProxyUrl: String? = null val imageProxyUrl: String? = null,
) )

View File

@ -19,7 +19,7 @@ data class PipedStream(
val height: Int? = null, val height: Int? = null,
val fps: Int? = null, val fps: Int? = null,
val audioTrackName: String? = null, val audioTrackName: String? = null,
val audioTrackId: String? = null val audioTrackId: String? = null,
) { ) {
fun getQualityString(fileName: String): String { fun getQualityString(fileName: String): String {
return "${fileName}_${quality?.replace(" ", "_")}_$format." + return "${fileName}_${quality?.replace(" ", "_")}_$format." +

View File

@ -13,7 +13,7 @@ data class Playlist(
val uploaderUrl: String? = null, val uploaderUrl: String? = null,
val uploaderAvatar: String? = null, val uploaderAvatar: String? = null,
val videos: Int = 0, val videos: Int = 0,
val relatedStreams: List<StreamItem> = emptyList() val relatedStreams: List<StreamItem> = emptyList(),
) { ) {
fun toPlaylistBookmark(playlistId: String): PlaylistBookmark { fun toPlaylistBookmark(playlistId: String): PlaylistBookmark {
return PlaylistBookmark( return PlaylistBookmark(
@ -22,7 +22,7 @@ data class Playlist(
thumbnailUrl = thumbnailUrl, thumbnailUrl = thumbnailUrl,
uploader = uploader, uploader = uploader,
uploaderAvatar = uploaderAvatar, uploaderAvatar = uploaderAvatar,
uploaderUrl = uploaderUrl uploaderUrl = uploaderUrl,
) )
} }
} }

View File

@ -8,5 +8,5 @@ data class PlaylistId(
val videoId: String? = null, val videoId: String? = null,
val videoIds: List<String> = emptyList(), val videoIds: List<String> = emptyList(),
val newName: String? = null, val newName: String? = null,
val index: Int = -1 val index: Int = -1,
) )

View File

@ -8,5 +8,5 @@ data class Playlists(
var name: String? = null, var name: String? = null,
val shortDescription: String? = null, val shortDescription: String? = null,
val thumbnail: String? = null, val thumbnail: String? = null,
val videos: Long = 0 val videos: Long = 0,
) )

View File

@ -10,5 +10,5 @@ data class PreviewFrames(
val totalCount: Int? = null, val totalCount: Int? = null,
val durationPerFrame: Int? = null, val durationPerFrame: Int? = null,
val framesPerPageX: Int? = null, val framesPerPageX: Int? = null,
val framesPerPageY: Int? = null val framesPerPageY: Int? = null,
) )

View File

@ -7,5 +7,5 @@ data class SearchResult(
val items: List<ContentItem> = emptyList(), val items: List<ContentItem> = emptyList(),
val nextpage: String? = null, val nextpage: String? = null,
val suggestion: String? = null, val suggestion: String? = null,
val corrected: Boolean? = null val corrected: Boolean? = null,
) )

View File

@ -12,5 +12,5 @@ data class Segment(
val segment: List<Double> = listOf(), val segment: List<Double> = listOf(),
val userID: String? = null, val userID: String? = null,
val videoDuration: Double? = null, val videoDuration: Double? = null,
val votes: Int? = null val votes: Int? = null,
) )

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class SegmentData( data class SegmentData(
val hash: String? = null, val hash: String? = null,
val segments: List<Segment> = listOf(), val segments: List<Segment> = listOf(),
val videoID: String? = null val videoID: String? = null,
) )

View File

@ -19,7 +19,7 @@ data class StreamItem(
val uploaderVerified: Boolean? = null, val uploaderVerified: Boolean? = null,
val uploaded: Long? = null, val uploaded: Long? = null,
val shortDescription: String? = null, val shortDescription: String? = null,
val isShort: Boolean = false val isShort: Boolean = false,
) { ) {
fun toLocalPlaylistItem(playlistId: String): LocalPlaylistItem { fun toLocalPlaylistItem(playlistId: String): LocalPlaylistItem {
return LocalPlaylistItem( return LocalPlaylistItem(
@ -31,7 +31,7 @@ data class StreamItem(
uploaderUrl = uploaderUrl, uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar, uploaderAvatar = uploaderAvatar,
uploadDate = uploadedDate, uploadDate = uploadedDate,
duration = duration duration = duration,
) )
} }
} }

View File

@ -3,9 +3,9 @@ package com.github.libretube.api.obj
import com.github.libretube.db.obj.DownloadItem import com.github.libretube.db.obj.DownloadItem
import com.github.libretube.enums.FileType import com.github.libretube.enums.FileType
import com.github.libretube.helpers.ProxyHelper import com.github.libretube.helpers.ProxyHelper
import java.nio.file.Paths
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import java.nio.file.Paths
@Serializable @Serializable
data class Streams( data class Streams(
@ -33,7 +33,7 @@ data class Streams(
val proxyUrl: String? = null, val proxyUrl: String? = null,
val chapters: List<ChapterSegment> = emptyList(), val chapters: List<ChapterSegment> = emptyList(),
val uploaderSubscriberCount: Long = 0, val uploaderSubscriberCount: Long = 0,
val previewFrames: List<PreviewFrames> = emptyList() val previewFrames: List<PreviewFrames> = emptyList(),
) { ) {
fun toDownloadItems( fun toDownloadItems(
videoId: String, videoId: String,
@ -42,7 +42,7 @@ data class Streams(
videoQuality: String?, videoQuality: String?,
audioFormat: String?, audioFormat: String?,
audioQuality: String?, audioQuality: String?,
subtitleCode: String? subtitleCode: String?,
): List<DownloadItem> { ): List<DownloadItem> {
val items = mutableListOf<DownloadItem>() val items = mutableListOf<DownloadItem>()
@ -58,8 +58,8 @@ data class Streams(
path = Paths.get(""), path = Paths.get(""),
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) }, url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
format = videoFormat, format = videoFormat,
quality = videoQuality quality = videoQuality,
) ),
) )
} }
@ -75,8 +75,8 @@ data class Streams(
path = Paths.get(""), path = Paths.get(""),
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) }, url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
format = audioFormat, format = audioFormat,
quality = audioQuality quality = audioQuality,
) ),
) )
} }
@ -89,8 +89,8 @@ data class Streams(
path = Paths.get(""), path = Paths.get(""),
url = subtitles.find { url = subtitles.find {
it.code == subtitleCode it.code == subtitleCode
}?.url?.let { ProxyHelper.unwrapIfEnabled(it) } }?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
) ),
) )
} }
@ -110,7 +110,7 @@ data class Streams(
duration = duration, duration = duration,
views = views, views = views,
uploaderVerified = uploaderVerified, uploaderVerified = uploaderVerified,
shortDescription = description shortDescription = description,
) )
} }
} }

View File

@ -7,5 +7,5 @@ data class Subscription(
val url: String, val url: String,
val name: String, val name: String,
val avatar: String, val avatar: String,
val verified: Boolean val verified: Boolean,
) )

View File

@ -8,5 +8,5 @@ data class Subtitle(
val mimeType: String? = null, val mimeType: String? = null,
val name: String? = null, val name: String? = null,
val code: String? = null, val code: String? = null,
val autoGenerated: Boolean? = null val autoGenerated: Boolean? = null,
) )

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
data class Token( data class Token(
val token: String? = null, val token: String? = null,
val error: String? = null val error: String? = null,
) )

View File

@ -17,7 +17,7 @@ class PictureInPictureParamsCompat private constructor(
private val title: CharSequence?, private val title: CharSequence?,
private val subtitle: CharSequence?, private val subtitle: CharSequence?,
private val aspectRatio: Rational?, private val aspectRatio: Rational?,
private val expandedAspectRatio: Rational? private val expandedAspectRatio: Rational?,
) { ) {
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
fun toPictureInPictureParams(): PictureInPictureParams { fun toPictureInPictureParams(): PictureInPictureParams {
@ -109,7 +109,7 @@ class PictureInPictureParamsCompat private constructor(
title, title,
subtitle, subtitle,
aspectRatio, aspectRatio,
expandedAspectRatio expandedAspectRatio,
) )
} }
} }

View File

@ -21,8 +21,6 @@ const val FAQ_URL = "https://libre-tube.github.io/#faq"
const val MATRIX_URL = "https://matrix.to/#/#LibreTube:matrix.org" const val MATRIX_URL = "https://matrix.to/#/#LibreTube:matrix.org"
const val MASTODON_URL = "https://fosstodon.org/@libretube" const val MASTODON_URL = "https://fosstodon.org/@libretube"
const val TELEGRAM_URL = "https://t.me/libretube" const val TELEGRAM_URL = "https://t.me/libretube"
const val DISCORD_URL = "https://discord.com/invite/Qc34xCj2GV"
const val REDDIT_URL = "https://www.reddit.com/r/Libretube/"
/** /**
* Share Dialog * Share Dialog

View File

@ -38,6 +38,7 @@ object PreferenceKeys {
const val ALTERNATIVE_VIDEOS_LAYOUT = "alternative_videos_layout" const val ALTERNATIVE_VIDEOS_LAYOUT = "alternative_videos_layout"
const val NEW_VIDEOS_BADGE = "new_videos_badge" const val NEW_VIDEOS_BADGE = "new_videos_badge"
const val PLAYLISTS_ORDER = "playlists_order" const val PLAYLISTS_ORDER = "playlists_order"
const val PLAYLIST_SORT_ORDER = "playlist_sort_order"
/** /**
* Instance * Instance
@ -97,6 +98,7 @@ object PreferenceKeys {
const val AUTO_FULLSCREEN_SHORTS = "auto_fullscreen_shorts" const val AUTO_FULLSCREEN_SHORTS = "auto_fullscreen_shorts"
const val PLAY_AUTOMATICALLY = "play_automatically" const val PLAY_AUTOMATICALLY = "play_automatically"
const val FULLSCREEN_GESTURES = "fullscreen_gestures" const val FULLSCREEN_GESTURES = "fullscreen_gestures"
const val UNLIMITED_SEARCH_HISTORY = "unlimited_search_history"
/** /**
* Background mode * Background mode
@ -108,6 +110,7 @@ object PreferenceKeys {
*/ */
const val NOTIFICATION_ENABLED = "notification_toggle" const val NOTIFICATION_ENABLED = "notification_toggle"
const val SHOW_STREAM_THUMBNAILS = "show_stream_thumbnails" const val SHOW_STREAM_THUMBNAILS = "show_stream_thumbnails"
const val SHORTS_NOTIFICATIONS = "shorts_notifications"
const val CHECKING_FREQUENCY = "checking_frequency" const val CHECKING_FREQUENCY = "checking_frequency"
const val REQUIRED_NETWORK = "required_network" const val REQUIRED_NETWORK = "required_network"
const val IGNORED_NOTIFICATION_CHANNELS = "ignored_notification_channels" const val IGNORED_NOTIFICATION_CHANNELS = "ignored_notification_channels"

View File

@ -37,15 +37,15 @@ import com.github.libretube.db.obj.WatchPosition
LocalPlaylistItem::class, LocalPlaylistItem::class,
Download::class, Download::class,
DownloadItem::class, DownloadItem::class,
SubscriptionGroup::class SubscriptionGroup::class,
], ],
version = 11, version = 11,
autoMigrations = [ autoMigrations = [
AutoMigration(from = 7, to = 8), AutoMigration(from = 7, to = 8),
AutoMigration(from = 8, to = 9), AutoMigration(from = 8, to = 9),
AutoMigration(from = 9, to = 10), AutoMigration(from = 9, to = 10),
AutoMigration(from = 10, to = 11) AutoMigration(from = 10, to = 11),
] ],
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {

View File

@ -2,12 +2,12 @@ package com.github.libretube.db
import androidx.room.TypeConverter import androidx.room.TypeConverter
import com.github.libretube.api.JsonHelper import com.github.libretube.api.JsonHelper
import java.nio.file.Path
import java.nio.file.Paths
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.toLocalDate import kotlinx.datetime.toLocalDate
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import java.nio.file.Path
import java.nio.file.Paths
object Converters { object Converters {
@TypeConverter @TypeConverter

View File

@ -22,7 +22,7 @@ object DatabaseHelper {
streams.uploaderUrl.toID(), streams.uploaderUrl.toID(),
streams.uploaderAvatar, streams.uploaderAvatar,
streams.thumbnailUrl, streams.thumbnailUrl,
streams.duration streams.duration,
) )
Database.watchHistoryDao().insert(watchHistoryItem) Database.watchHistoryDao().insert(watchHistoryItem)
val maxHistorySize = PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "100") val maxHistorySize = PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "100")
@ -40,10 +40,14 @@ object DatabaseHelper {
suspend fun addToSearchHistory(searchHistoryItem: SearchHistoryItem) { suspend fun addToSearchHistory(searchHistoryItem: SearchHistoryItem) {
Database.searchHistoryDao().insert(searchHistoryItem) Database.searchHistoryDao().insert(searchHistoryItem)
if (PreferenceHelper.getBoolean(PreferenceKeys.UNLIMITED_SEARCH_HISTORY, false)) return
// delete the first watch history entry if the limit is reached // delete the first watch history entry if the limit is reached
val searchHistory = Database.searchHistoryDao().getAll() val searchHistory = Database.searchHistoryDao().getAll().toMutableList()
if (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
while (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
Database.searchHistoryDao().delete(searchHistory.first()) Database.searchHistoryDao().delete(searchHistory.first())
searchHistory.removeFirst()
} }
} }
} }

View File

@ -10,5 +10,5 @@ import kotlinx.serialization.Serializable
class CustomInstance( class CustomInstance(
@PrimaryKey var name: String = "", @PrimaryKey var name: String = "",
@ColumnInfo var apiUrl: String = "", @ColumnInfo var apiUrl: String = "",
@ColumnInfo var frontendUrl: String = "" @ColumnInfo var frontendUrl: String = "",
) )

View File

@ -2,8 +2,8 @@ package com.github.libretube.db.obj
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import java.nio.file.Path
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import java.nio.file.Path
@Entity(tableName = "download") @Entity(tableName = "download")
data class Download( data class Download(
@ -13,5 +13,5 @@ data class Download(
val description: String = "", val description: String = "",
val uploader: String = "", val uploader: String = "",
val uploadDate: LocalDate? = null, val uploadDate: LocalDate? = null,
val thumbnailPath: Path? = null val thumbnailPath: Path? = null,
) )

View File

@ -15,9 +15,9 @@ import java.nio.file.Path
entity = Download::class, entity = Download::class,
parentColumns = ["videoId"], parentColumns = ["videoId"],
childColumns = ["videoId"], childColumns = ["videoId"],
onDelete = ForeignKey.CASCADE onDelete = ForeignKey.CASCADE,
) ),
] ],
) )
data class DownloadItem( data class DownloadItem(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
@ -29,5 +29,5 @@ data class DownloadItem(
var url: String? = null, var url: String? = null,
var format: String? = null, var format: String? = null,
var quality: String? = null, var quality: String? = null,
var downloadSize: Long = -1L var downloadSize: Long = -1L,
) )

View File

@ -7,7 +7,7 @@ data class DownloadWithItems(
@Embedded val download: Download, @Embedded val download: Download,
@Relation( @Relation(
parentColumn = "videoId", parentColumn = "videoId",
entityColumn = "videoId" entityColumn = "videoId",
) )
val downloadItems: List<DownloadItem> val downloadItems: List<DownloadItem>,
) )

View File

@ -10,5 +10,5 @@ data class LocalPlaylist(
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
val id: Int = 0, val id: Int = 0,
var name: String = "", var name: String = "",
var thumbnailUrl: String = "" var thumbnailUrl: String = "",
) )

View File

@ -19,7 +19,7 @@ data class LocalPlaylistItem(
@ColumnInfo val uploaderUrl: String? = null, @ColumnInfo val uploaderUrl: String? = null,
@ColumnInfo val uploaderAvatar: String? = null, @ColumnInfo val uploaderAvatar: String? = null,
@ColumnInfo val thumbnailUrl: String? = null, @ColumnInfo val thumbnailUrl: String? = null,
@ColumnInfo val duration: Long? = null @ColumnInfo val duration: Long? = null,
) { ) {
fun toStreamItem(): StreamItem { fun toStreamItem(): StreamItem {
return StreamItem( return StreamItem(
@ -31,7 +31,7 @@ data class LocalPlaylistItem(
uploaderAvatar = ProxyHelper.rewriteUrl(uploaderAvatar), uploaderAvatar = ProxyHelper.rewriteUrl(uploaderAvatar),
uploadedDate = uploadDate, uploadedDate = uploadDate,
uploaded = null, uploaded = null,
duration = duration duration = duration,
) )
} }
} }

View File

@ -9,7 +9,7 @@ data class LocalPlaylistWithVideos(
@Embedded val playlist: LocalPlaylist = LocalPlaylist(), @Embedded val playlist: LocalPlaylist = LocalPlaylist(),
@Relation( @Relation(
parentColumn = "id", parentColumn = "id",
entityColumn = "playlistId" entityColumn = "playlistId",
) )
val videos: List<LocalPlaylistItem> = listOf() val videos: List<LocalPlaylistItem> = listOf(),
) )

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@Entity(tableName = "localSubscription") @Entity(tableName = "localSubscription")
data class LocalSubscription( data class LocalSubscription(
@PrimaryKey val channelId: String = "" @PrimaryKey val channelId: String = "",
) )

View File

@ -13,5 +13,5 @@ data class PlaylistBookmark(
var thumbnailUrl: String? = null, var thumbnailUrl: String? = null,
var uploader: String? = null, var uploader: String? = null,
var uploaderUrl: String? = null, var uploaderUrl: String? = null,
var uploaderAvatar: String? = null var uploaderAvatar: String? = null,
) )

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@Entity(tableName = "searchHistoryItem") @Entity(tableName = "searchHistoryItem")
data class SearchHistoryItem( data class SearchHistoryItem(
@PrimaryKey val query: String = "" @PrimaryKey val query: String = "",
) )

View File

@ -8,5 +8,5 @@ import kotlinx.serialization.Serializable
@Entity(tableName = "subscriptionGroups") @Entity(tableName = "subscriptionGroups")
data class SubscriptionGroup( data class SubscriptionGroup(
@PrimaryKey var name: String, @PrimaryKey var name: String,
val channels: MutableList<String> val channels: MutableList<String>,
) )

View File

@ -16,5 +16,5 @@ data class WatchHistoryItem(
@ColumnInfo val uploaderUrl: String? = null, @ColumnInfo val uploaderUrl: String? = null,
@ColumnInfo var uploaderAvatar: String? = null, @ColumnInfo var uploaderAvatar: String? = null,
@ColumnInfo var thumbnailUrl: String? = null, @ColumnInfo var thumbnailUrl: String? = null,
@ColumnInfo val duration: Long? = null @ColumnInfo val duration: Long? = null,
) )

View File

@ -9,5 +9,5 @@ import kotlinx.serialization.Serializable
@Entity(tableName = "watchPosition") @Entity(tableName = "watchPosition")
data class WatchPosition( data class WatchPosition(
@PrimaryKey val videoId: String = "", @PrimaryKey val videoId: String = "",
@ColumnInfo val position: Long = 0L @ColumnInfo val position: Long = 0L,
) )

View File

@ -2,5 +2,5 @@ package com.github.libretube.enums
enum class AudioQuality { enum class AudioQuality {
BEST, BEST,
WORST WORST,
} }

View File

@ -3,5 +3,5 @@ package com.github.libretube.enums
enum class FileType { enum class FileType {
AUDIO, AUDIO,
VIDEO, VIDEO,
SUBTITLE SUBTITLE,
} }

View File

@ -7,7 +7,8 @@ enum class PlayerEvent(val value: Int) {
Rewind(3), Rewind(3),
Next(5), Next(5),
Prev(6), Prev(6),
Background(7); Background(7),
;
companion object { companion object {
fun fromInt(value: Int) = PlayerEvent.values().first { it.value == value } fun fromInt(value: Int) = PlayerEvent.values().first { it.value == value }

View File

@ -14,5 +14,5 @@ enum class PlaylistType {
/** /**
* YouTube playlist * YouTube playlist
*/ */
PUBLIC PUBLIC,
} }

View File

@ -3,5 +3,5 @@ package com.github.libretube.enums
enum class ShareObjectType { enum class ShareObjectType {
VIDEO, VIDEO,
PLAYLIST, PLAYLIST,
CHANNEL CHANNEL,
} }

View File

@ -1,9 +1,9 @@
package com.github.libretube.extensions package com.github.libretube.extensions
import java.net.HttpURLConnection
import java.net.URL
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.HttpURLConnection
import java.net.URL
suspend fun URL.getContentLength(def: Long = -1): Long { suspend fun URL.getContentLength(def: Long = -1): Long {
try { try {

View File

@ -5,7 +5,7 @@ import kotlin.math.roundToInt
fun Float.round(decimalPlaces: Int): Float { fun Float.round(decimalPlaces: Int): Float {
return (this * 10.0.pow(decimalPlaces.toDouble())).roundToInt() / 10.0.pow( return (this * 10.0.pow(decimalPlaces.toDouble())).roundToInt() / 10.0.pow(
decimalPlaces.toDouble() decimalPlaces.toDouble(),
) )
.toFloat() .toFloat()
} }

View File

@ -12,7 +12,7 @@ fun Context.toastFromMainThread(text: String) {
Toast.makeText( Toast.makeText(
this, this,
text, text,
Toast.LENGTH_SHORT Toast.LENGTH_SHORT,
).show() ).show()
} }
} }

View File

@ -3,5 +3,5 @@ package com.github.libretube.extensions
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
inline fun DefaultTrackSelector.updateParameters( inline fun DefaultTrackSelector.updateParameters(
actions: DefaultTrackSelector.Parameters.Builder.() -> Unit actions: DefaultTrackSelector.Parameters.Builder.() -> Unit,
) = setParameters(buildUponParameters().apply(actions)) ) = setParameters(buildUponParameters().apply(actions))

View File

@ -27,7 +27,7 @@ object BackgroundHelper {
playlistId: String? = null, playlistId: String? = null,
channelId: String? = null, channelId: String? = null,
keepQueue: Boolean? = null, keepQueue: Boolean? = null,
keepVideoPlayerAlive: Boolean = false keepVideoPlayerAlive: Boolean = false,
) { ) {
// close the previous video player if open // close the previous video player if open
if (!keepVideoPlayerAlive) { if (!keepVideoPlayerAlive) {

View File

@ -56,7 +56,7 @@ class BrightnessHelper(private val activity: Activity) {
value: Float, value: Float,
maxValue: Float, maxValue: Float,
minValue: Float = 0.0f, minValue: Float = 0.0f,
shouldSave: Boolean = false shouldSave: Boolean = false,
) { ) {
brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness) brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness)
if (shouldSave) savedBrightness = brightness if (shouldSave) savedBrightness = brightness
@ -69,7 +69,7 @@ class BrightnessHelper(private val activity: Activity) {
fun getBrightnessWithScale( fun getBrightnessWithScale(
maxValue: Float, maxValue: Float,
minValue: Float = 0.0f, minValue: Float = 0.0f,
saved: Boolean = false saved: Boolean = false,
): Float { ): Float {
return if (saved) { return if (saved) {
savedBrightness.normalize(minBrightness, maxBrightness, minValue, maxValue) savedBrightness.normalize(minBrightness, maxBrightness, minValue, maxValue)

View File

@ -2,14 +2,14 @@ package com.github.libretube.helpers
import com.github.libretube.api.obj.PipedStream import com.github.libretube.api.obj.PipedStream
import com.github.libretube.api.obj.Streams import com.github.libretube.api.obj.Streams
import org.w3c.dom.Document
import org.w3c.dom.Element
import java.io.StringWriter import java.io.StringWriter
import javax.xml.parsers.DocumentBuilder import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult import javax.xml.transform.stream.StreamResult
import org.w3c.dom.Document
import org.w3c.dom.Element
// Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js // Based off of https://github.com/TeamPiped/Piped/blob/master/src/utils/DashUtils.js
@ -21,10 +21,10 @@ object DashHelper {
private data class AdapSetInfo( private data class AdapSetInfo(
val mimeType: String, val mimeType: String,
val audioTrackId: String? = null, val audioTrackId: String? = null,
val formats: MutableList<PipedStream> = mutableListOf() val formats: MutableList<PipedStream> = mutableListOf(),
) )
fun createManifest(streams: Streams): String { fun createManifest(streams: Streams, supportsHdr: Boolean): String {
val builder: DocumentBuilder = builderFactory.newDocumentBuilder() val builder: DocumentBuilder = builderFactory.newDocumentBuilder()
val doc = builder.newDocument() val doc = builder.newDocument()
@ -40,17 +40,18 @@ object DashHelper {
val adapSetInfos = ArrayList<AdapSetInfo>() val adapSetInfos = ArrayList<AdapSetInfo>()
val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs val enabledVideoCodecs = PlayerHelper.enabledVideoCodecs
for (stream in streams.videoStreams for (
stream in streams.videoStreams
// used to avoid including LBRY HLS inside the streams in the manifest // used to avoid including LBRY HLS inside the streams in the manifest
.filter { !it.format.orEmpty().contains("HLS") } .filter { !it.format.orEmpty().contains("HLS") }
// filter the codecs according to the user's preferences // filter the codecs according to the user's preferences
.filter { .filter {
if (enabledVideoCodecs != "all") { enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith(
it.codec?.lowercase()?.startsWith(enabledVideoCodecs) ?: true enabledVideoCodecs,
} else { )
true }
} .filter { supportsHdr || !it.quality.orEmpty().uppercase().contains("HDR") }
}) { ) {
// ignore dual format streams // ignore dual format streams
if (!stream.videoOnly!!) { if (!stream.videoOnly!!) {
continue continue
@ -70,8 +71,8 @@ object DashHelper {
AdapSetInfo( AdapSetInfo(
stream.mimeType!!, stream.mimeType!!,
null, null,
mutableListOf(stream) mutableListOf(stream),
) ),
) )
} }
@ -88,8 +89,8 @@ object DashHelper {
AdapSetInfo( AdapSetInfo(
stream.mimeType!!, stream.mimeType!!,
stream.audioTrackId, stream.audioTrackId,
mutableListOf(stream) mutableListOf(stream),
) ),
) )
} }
@ -144,7 +145,7 @@ object DashHelper {
val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration") val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration")
audioChannelConfiguration.setAttribute( audioChannelConfiguration.setAttribute(
"schemeIdUri", "schemeIdUri",
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011" "urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
) )
audioChannelConfiguration.setAttribute("value", "2") audioChannelConfiguration.setAttribute("value", "2")

View File

@ -0,0 +1,22 @@
package com.github.libretube.helpers
import android.content.Context
import android.hardware.display.DisplayManager
import android.os.Build
import android.view.Display
object DisplayHelper {
/**
* Detect whether the device supports HDR as the ExoPlayer doesn't handle it properly
* Returns false on and below SDK 24
*/
fun supportsHdr(context: Context): Boolean {
val displayManager = context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
val display = displayManager.getDisplay(Display.DEFAULT_DISPLAY)
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
display.hdrCapabilities.supportedHdrTypes.isNotEmpty()
} else {
false
}
}
}

View File

@ -44,7 +44,7 @@ object DownloadHelper {
fun getMaxConcurrentDownloads(): Int { fun getMaxConcurrentDownloads(): Int {
return PreferenceHelper.getString( return PreferenceHelper.getString(
PreferenceKeys.MAX_CONCURRENT_DOWNLOADS, PreferenceKeys.MAX_CONCURRENT_DOWNLOADS,
"6" "6",
).toFloat().toInt() ).toFloat().toInt()
} }
@ -56,7 +56,7 @@ object DownloadHelper {
videoQuality: String? = null, videoQuality: String? = null,
audioFormat: String? = null, audioFormat: String? = null,
audioQuality: String? = null, audioQuality: String? = null,
subtitleCode: String? = null subtitleCode: String? = null,
) { ) {
val intent = Intent(context, DownloadService::class.java) val intent = Intent(context, DownloadService::class.java)

View File

@ -28,7 +28,7 @@ object ImageHelper {
fun initializeImageLoader(context: Context) { fun initializeImageLoader(context: Context) {
val maxImageCacheSize = PreferenceHelper.getString( val maxImageCacheSize = PreferenceHelper.getString(
PreferenceKeys.MAX_IMAGE_CACHE, PreferenceKeys.MAX_IMAGE_CACHE,
"" "",
) )
imageLoader = ImageLoader.Builder(context) imageLoader = ImageLoader.Builder(context)
@ -42,7 +42,7 @@ object ImageHelper {
DiskCache.Builder() DiskCache.Builder()
.directory(context.cacheDir.resolve("coil")) .directory(context.cacheDir.resolve("coil"))
.maxSizeBytes(maxImageCacheSize.toInt() * 1024 * 1024L) .maxSizeBytes(maxImageCacheSize.toInt() * 1024 * 1024L)
.build() .build(),
) )
} }
} }
@ -109,7 +109,7 @@ object ImageHelper {
(bitmap.width - newSize) / 2, (bitmap.width - newSize) / 2,
(bitmap.height - newSize) / 2, (bitmap.height - newSize) / 2,
newSize, newSize,
newSize newSize,
) )
} }
} }

View File

@ -15,10 +15,10 @@ import com.github.libretube.obj.ImportPlaylist
import com.github.libretube.obj.ImportPlaylistFile import com.github.libretube.obj.ImportPlaylistFile
import com.github.libretube.obj.NewPipeSubscription import com.github.libretube.obj.NewPipeSubscription
import com.github.libretube.obj.NewPipeSubscriptions import com.github.libretube.obj.NewPipeSubscriptions
import kotlin.streams.toList
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream import kotlinx.serialization.json.encodeToStream
import kotlin.streams.toList
object ImportHelper { object ImportHelper {
/** /**
@ -44,6 +44,7 @@ object ImportHelper {
/** /**
* Get a list of channel IDs from a file [Uri] * Get a list of channel IDs from a file [Uri]
*/ */
@OptIn(ExperimentalSerializationApi::class)
private fun getChannelsFromUri(activity: Activity, uri: Uri): List<String> { private fun getChannelsFromUri(activity: Activity, uri: Uri): List<String> {
return when (val fileType = activity.contentResolver.getType(uri)) { return when (val fileType = activity.contentResolver.getType(uri)) {
"application/json", "application/*", "application/octet-stream" -> { "application/json", "application/*", "application/octet-stream" -> {
@ -72,6 +73,7 @@ object ImportHelper {
/** /**
* Write the text to the document * Write the text to the document
*/ */
@OptIn(ExperimentalSerializationApi::class)
suspend fun exportSubscriptions(activity: Activity, uri: Uri) { suspend fun exportSubscriptions(activity: Activity, uri: Uri) {
val token = PreferenceHelper.getToken() val token = PreferenceHelper.getToken()
val subs = if (token.isNotEmpty()) { val subs = if (token.isNotEmpty()) {
@ -105,10 +107,14 @@ object ImportHelper {
activity.contentResolver.openInputStream(uri)?.use { activity.contentResolver.openInputStream(uri)?.use {
val lines = it.bufferedReader().use { reader -> reader.lines().toList() } val lines = it.bufferedReader().use { reader -> reader.lines().toList() }
playlist.name = lines[1].split(",").reversed()[2] playlist.name = lines[1].split(",").reversed()[2]
val splitIndex = lines.indexOfFirst { line -> line.isBlank() } var splitIndex = lines.indexOfFirst { line -> line.isBlank() }
// seek until playlist items table
while (lines.getOrNull(splitIndex + 1).orEmpty().isBlank()) {
splitIndex++
}
lines.subList(splitIndex + 2, lines.size).forEach { line -> lines.subList(splitIndex + 2, lines.size).forEach { line ->
line.split(",").firstOrNull()?.let { videoId -> line.split(",").firstOrNull()?.let { videoId ->
if (videoId.isNotBlank()) playlist.videos = playlist.videos + videoId if (videoId.isNotBlank()) playlist.videos += videoId.trim()
} }
} }
importPlaylists.add(playlist) importPlaylists.add(playlist)
@ -145,6 +151,7 @@ object ImportHelper {
/** /**
* Export Playlists * Export Playlists
*/ */
@OptIn(ExperimentalSerializationApi::class)
suspend fun exportPlaylists(activity: Activity, uri: Uri) { suspend fun exportPlaylists(activity: Activity, uri: Uri) {
val playlists = PlaylistsHelper.exportPlaylists() val playlists = PlaylistsHelper.exportPlaylists()
val playlistFile = ImportPlaylistFile("Piped", 1, playlists) val playlistFile = ImportPlaylistFile("Piped", 1, playlists)

View File

@ -21,7 +21,7 @@ object IntentHelper {
context.packageManager context.packageManager
.queryIntentActivities( .queryIntentActivities(
intent, intent,
PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong()) PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_ALL.toLong()),
) )
} else { } else {
context.packageManager context.packageManager

View File

@ -20,7 +20,7 @@ object LocaleHelper {
val languageParts = languageName.split("-") val languageParts = languageName.split("-")
Locale( Locale(
languageParts[0], languageParts[0],
languageParts[1].replace("r", "") languageParts[1].replace("r", ""),
) )
} }
else -> Locale(languageName) else -> Locale(languageName)

View File

@ -23,7 +23,7 @@ object NavBarHelper {
val prefItems = try { val prefItems = try {
PreferenceHelper.getString( PreferenceHelper.getString(
PreferenceKeys.NAVBAR_ITEMS, PreferenceKeys.NAVBAR_ITEMS,
"" "",
).split(SEPARATOR) ).split(SEPARATOR)
} catch (e: Exception) { } catch (e: Exception) {
Log.e("fail to parse nav items", e.toString()) Log.e("fail to parse nav items", e.toString())
@ -38,7 +38,7 @@ object NavBarHelper {
navBarItems.add( navBarItems.add(
p.menu[it.replace("-", "").toInt()].apply { p.menu[it.replace("-", "").toInt()].apply {
this.isVisible = !it.contains("-") this.isVisible = !it.contains("-")
} },
) )
} }
return navBarItems return navBarItems
@ -65,7 +65,7 @@ object NavBarHelper {
} }
PreferenceHelper.putString( PreferenceHelper.putString(
PreferenceKeys.NAVBAR_ITEMS, PreferenceKeys.NAVBAR_ITEMS,
prefString.joinToString(SEPARATOR) prefString.joinToString(SEPARATOR),
) )
} }
@ -90,7 +90,7 @@ object NavBarHelper {
// remove the old items // remove the old items
navBarItems.forEach { navBarItems.forEach {
menuItems.add( menuItems.add(
bottomNav.menu.findItem(it.itemId) bottomNav.menu.findItem(it.itemId),
) )
bottomNav.menu.removeItem(it.itemId) bottomNav.menu.removeItem(it.itemId)
} }
@ -103,7 +103,7 @@ object NavBarHelper {
menuItem.groupId, menuItem.groupId,
menuItem.itemId, menuItem.itemId,
Menu.NONE, Menu.NONE,
menuItem.title menuItem.title,
).icon = menuItem.icon ).icon = menuItem.icon
} }
} }

View File

@ -27,7 +27,7 @@ object NavigationHelper {
fun navigateChannel( fun navigateChannel(
context: Context, context: Context,
channelId: String? channelId: String?,
) { ) {
if (channelId == null) return if (channelId == null) return
@ -64,7 +64,7 @@ object NavigationHelper {
channelId: String? = null, channelId: String? = null,
keepQueue: Boolean = false, keepQueue: Boolean = false,
timeStamp: Long? = null, timeStamp: Long? = null,
forceVideo: Boolean = false forceVideo: Boolean = false,
) { ) {
if (videoId == null) return if (videoId == null) return
@ -76,7 +76,7 @@ object NavigationHelper {
timeStamp, timeStamp,
playlistId, playlistId,
channelId, channelId,
keepQueue keepQueue,
) )
handler.postDelayed(500) { handler.postDelayed(500) {
startAudioPlayer(context) startAudioPlayer(context)
@ -89,7 +89,7 @@ object NavigationHelper {
IntentData.playlistId to playlistId, IntentData.playlistId to playlistId,
IntentData.channelId to channelId, IntentData.channelId to channelId,
IntentData.keepQueue to keepQueue, IntentData.keepQueue to keepQueue,
IntentData.timeStamp to timeStamp IntentData.timeStamp to timeStamp,
) )
val activity = unwrapActivity(context) val activity = unwrapActivity(context)
@ -101,14 +101,14 @@ object NavigationHelper {
fun navigatePlaylist( fun navigatePlaylist(
context: Context, context: Context,
playlistId: String?, playlistId: String?,
playlistType: PlaylistType playlistType: PlaylistType,
) { ) {
if (playlistId == null) return if (playlistId == null) return
val activity = unwrapActivity(context) val activity = unwrapActivity(context)
val bundle = bundleOf( val bundle = bundleOf(
IntentData.playlistId to playlistId, IntentData.playlistId to playlistId,
IntentData.playlistType to playlistType IntentData.playlistType to playlistType,
) )
activity.navController.navigate(R.id.playlistFragment, bundle) activity.navController.navigate(R.id.playlistFragment, bundle)
} }

View File

@ -10,6 +10,7 @@ object NetworkHelper {
/** /**
* Detect whether network is available * Detect whether network is available
*/ */
@Suppress("DEPRECATION")
fun isNetworkAvailable(context: Context): Boolean { fun isNetworkAvailable(context: Context): Boolean {
// In case we are using a VPN, we return true since we might be using reverse tethering // In case we are using a VPN, we return true since we might be using reverse tethering
val connectivityManager = context.getSystemService<ConnectivityManager>() ?: return false val connectivityManager = context.getSystemService<ConnectivityManager>() ?: return false
@ -36,6 +37,7 @@ object NetworkHelper {
* @param context Context of the application * @param context Context of the application
* @return whether the network is metered or not * @return whether the network is metered or not
*/ */
@Suppress("DEPRECATION")
fun isNetworkMetered(context: Context): Boolean { fun isNetworkMetered(context: Context): Boolean {
val connectivityManager = context.getSystemService<ConnectivityManager>()!! val connectivityManager = context.getSystemService<ConnectivityManager>()!!
val activeNetworkInfo = connectivityManager.activeNetworkInfo val activeNetworkInfo = connectivityManager.activeNetworkInfo

View File

@ -17,18 +17,18 @@ object NotificationHelper {
*/ */
fun enqueueWork( fun enqueueWork(
context: Context, context: Context,
existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy existingPeriodicWorkPolicy: ExistingPeriodicWorkPolicy,
) { ) {
// get the notification preferences // get the notification preferences
PreferenceHelper.initialize(context) PreferenceHelper.initialize(context)
val notificationsEnabled = PreferenceHelper.getBoolean( val notificationsEnabled = PreferenceHelper.getBoolean(
PreferenceKeys.NOTIFICATION_ENABLED, PreferenceKeys.NOTIFICATION_ENABLED,
true true,
) )
val checkingFrequency = PreferenceHelper.getString( val checkingFrequency = PreferenceHelper.getString(
PreferenceKeys.CHECKING_FREQUENCY, PreferenceKeys.CHECKING_FREQUENCY,
"60" "60",
).toLong() ).toLong()
// schedule the work manager request if logged in and notifications enabled // schedule the work manager request if logged in and notifications enabled
@ -58,7 +58,7 @@ object NotificationHelper {
// create the worker // create the worker
val notificationWorker = PeriodicWorkRequestBuilder<NotificationWorker>( val notificationWorker = PeriodicWorkRequestBuilder<NotificationWorker>(
checkingFrequency, checkingFrequency,
TimeUnit.MINUTES TimeUnit.MINUTES,
) )
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()
@ -68,7 +68,7 @@ object NotificationHelper {
.enqueueUniquePeriodicWork( .enqueueUniquePeriodicWork(
NOTIFICATION_WORK_NAME, NOTIFICATION_WORK_NAME,
existingPeriodicWorkPolicy, existingPeriodicWorkPolicy,
notificationWorker notificationWorker,
) )
// for testing the notifications by the work manager // for testing the notifications by the work manager
@ -79,6 +79,6 @@ object NotificationHelper {
.setConstraints(constraints) .setConstraints(constraints)
.build() .build()
) )
*/ */
} }
} }

View File

@ -52,7 +52,7 @@ object PlayerHelper {
return getBitRate( return getBitRate(
filteredAudios, filteredAudios,
if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST if (audioQuality == "best") AudioQuality.BEST else AudioQuality.WORST,
) )
} }
@ -93,56 +93,56 @@ object PlayerHelper {
val categories: ArrayList<String> = arrayListOf() val categories: ArrayList<String> = arrayListOf()
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"intro_category_key", "intro_category_key",
false false,
) )
) { ) {
categories.add("intro") categories.add("intro")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"selfpromo_category_key", "selfpromo_category_key",
false false,
) )
) { ) {
categories.add("selfpromo") categories.add("selfpromo")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"interaction_category_key", "interaction_category_key",
false false,
) )
) { ) {
categories.add("interaction") categories.add("interaction")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"sponsors_category_key", "sponsors_category_key",
true true,
) )
) { ) {
categories.add("sponsor") categories.add("sponsor")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"outro_category_key", "outro_category_key",
false false,
) )
) { ) {
categories.add("outro") categories.add("outro")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"filler_category_key", "filler_category_key",
false false,
) )
) { ) {
categories.add("filler") categories.add("filler")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"music_offtopic_category_key", "music_offtopic_category_key",
false false,
) )
) { ) {
categories.add("music_offtopic") categories.add("music_offtopic")
} }
if (PreferenceHelper.getBoolean( if (PreferenceHelper.getBoolean(
"preview_category_key", "preview_category_key",
false false,
) )
) { ) {
categories.add("preview") categories.add("preview")
@ -153,7 +153,7 @@ object PlayerHelper {
fun getOrientation(videoWidth: Int, videoHeight: Int): Int { fun getOrientation(videoWidth: Int, videoHeight: Int): Int {
val fullscreenOrientationPref = PreferenceHelper.getString( val fullscreenOrientationPref = PreferenceHelper.getString(
PreferenceKeys.FULLSCREEN_ORIENTATION, PreferenceKeys.FULLSCREEN_ORIENTATION,
"ratio" "ratio",
) )
return when (fullscreenOrientationPref) { return when (fullscreenOrientationPref) {
@ -176,25 +176,25 @@ object PlayerHelper {
val autoRotationEnabled: Boolean val autoRotationEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.AUTO_FULLSCREEN, PreferenceKeys.AUTO_FULLSCREEN,
false false,
) )
val relatedStreamsEnabled: Boolean val relatedStreamsEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.RELATED_STREAMS, PreferenceKeys.RELATED_STREAMS,
true true,
) )
val pausePlayerOnScreenOffEnabled: Boolean val pausePlayerOnScreenOffEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.PAUSE_ON_SCREEN_OFF, PreferenceKeys.PAUSE_ON_SCREEN_OFF,
false false,
) )
private val watchPositionsPref: String private val watchPositionsPref: String
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.WATCH_POSITIONS, PreferenceKeys.WATCH_POSITIONS,
"always" "always",
) )
val watchPositionsVideo: Boolean val watchPositionsVideo: Boolean
@ -206,38 +206,38 @@ object PlayerHelper {
val watchHistoryEnabled: Boolean val watchHistoryEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.WATCH_HISTORY_TOGGLE, PreferenceKeys.WATCH_HISTORY_TOGGLE,
true true,
) )
val useSystemCaptionStyle: Boolean val useSystemCaptionStyle: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.SYSTEM_CAPTION_STYLE, PreferenceKeys.SYSTEM_CAPTION_STYLE,
true true,
) )
private val bufferingGoal: Int private val bufferingGoal: Int
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.BUFFERING_GOAL, PreferenceKeys.BUFFERING_GOAL,
"50" "50",
).toInt() * 1000 ).toInt() * 1000
val sponsorBlockEnabled: Boolean val sponsorBlockEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
"sb_enabled_key", "sb_enabled_key",
true true,
) )
private val sponsorBlockNotifications: Boolean private val sponsorBlockNotifications: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
"sb_notifications_key", "sb_notifications_key",
true true,
) )
val defaultSubtitleCode: String? val defaultSubtitleCode: String?
get() { get() {
val code = PreferenceHelper.getString( val code = PreferenceHelper.getString(
PreferenceKeys.DEFAULT_SUBTITLE, PreferenceKeys.DEFAULT_SUBTITLE,
"" "",
) )
if (code == "") return null if (code == "") return null
@ -251,37 +251,37 @@ object PlayerHelper {
val skipButtonsEnabled: Boolean val skipButtonsEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.SKIP_BUTTONS, PreferenceKeys.SKIP_BUTTONS,
false false,
) )
val pipEnabled: Boolean val pipEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.PICTURE_IN_PICTURE, PreferenceKeys.PICTURE_IN_PICTURE,
true true,
) )
val skipSegmentsManually: Boolean val skipSegmentsManually: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.SB_SKIP_MANUALLY, PreferenceKeys.SB_SKIP_MANUALLY,
false false,
) )
val autoPlayEnabled: Boolean val autoPlayEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.AUTO_PLAY, PreferenceKeys.AUTO_PLAY,
true true,
) )
val autoPlayCountdown: Boolean val autoPlayCountdown: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.AUTOPLAY_COUNTDOWN, PreferenceKeys.AUTOPLAY_COUNTDOWN,
false false,
) )
val seekIncrement: Long val seekIncrement: Long
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.SEEK_INCREMENT, PreferenceKeys.SEEK_INCREMENT,
"10.0" "10.0",
).toFloat() ).toFloat()
.roundToInt() .roundToInt()
.toLong() * 1000 .toLong() * 1000
@ -289,7 +289,7 @@ object PlayerHelper {
private val playbackSpeed: Float private val playbackSpeed: Float
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.PLAYBACK_SPEED, PreferenceKeys.PLAYBACK_SPEED,
"1" "1",
).replace("F", "").toFloat() ).replace("F", "").toFloat()
private val backgroundSpeed: Float private val backgroundSpeed: Float
@ -301,73 +301,73 @@ object PlayerHelper {
val resizeModePref: String val resizeModePref: String
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.PLAYER_RESIZE_MODE, PreferenceKeys.PLAYER_RESIZE_MODE,
"fit" "fit",
) )
val alternativeVideoLayout: Boolean val alternativeVideoLayout: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.ALTERNATIVE_PLAYER_LAYOUT, PreferenceKeys.ALTERNATIVE_PLAYER_LAYOUT,
false false,
) )
val autoInsertRelatedVideos: Boolean val autoInsertRelatedVideos: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.QUEUE_AUTO_INSERT_RELATED, PreferenceKeys.QUEUE_AUTO_INSERT_RELATED,
true true,
) )
val swipeGestureEnabled: Boolean val swipeGestureEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.PLAYER_SWIPE_CONTROLS, PreferenceKeys.PLAYER_SWIPE_CONTROLS,
true true,
) )
val fullscreenGesturesEnabled: Boolean val fullscreenGesturesEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.FULLSCREEN_GESTURES, PreferenceKeys.FULLSCREEN_GESTURES,
false false,
) )
val pinchGestureEnabled: Boolean val pinchGestureEnabled: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.PLAYER_PINCH_CONTROL, PreferenceKeys.PLAYER_PINCH_CONTROL,
true true,
) )
val captionsTextSize: Float val captionsTextSize: Float
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.CAPTIONS_SIZE, PreferenceKeys.CAPTIONS_SIZE,
"18" "18",
).toFloat() ).toFloat()
val doubleTapToSeek: Boolean val doubleTapToSeek: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.DOUBLE_TAP_TO_SEEK, PreferenceKeys.DOUBLE_TAP_TO_SEEK,
true true,
) )
val pauseOnQuit: Boolean val pauseOnQuit: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.PAUSE_ON_QUIT, PreferenceKeys.PAUSE_ON_QUIT,
false false,
) )
private val alternativePiPControls: Boolean private val alternativePiPControls: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.ALTERNATIVE_PIP_CONTROLS, PreferenceKeys.ALTERNATIVE_PIP_CONTROLS,
false false,
) )
private val skipSilence: Boolean private val skipSilence: Boolean
get() = PreferenceHelper.getBoolean( get() = PreferenceHelper.getBoolean(
PreferenceKeys.SKIP_SILENCE, PreferenceKeys.SKIP_SILENCE,
false false,
) )
val enabledVideoCodecs: String val enabledVideoCodecs: String
get() = PreferenceHelper.getString( get() = PreferenceHelper.getString(
PreferenceKeys.ENABLED_VIDEO_CODECS, PreferenceKeys.ENABLED_VIDEO_CODECS,
"all" "all",
) )
fun getDefaultResolution(context: Context): String { fun getDefaultResolution(context: Context): String {
@ -392,14 +392,14 @@ object PlayerHelper {
activity: Activity, activity: Activity,
id: Int, id: Int,
@StringRes title: Int, @StringRes title: Int,
event: PlayerEvent event: PlayerEvent,
): RemoteActionCompat { ): RemoteActionCompat {
val text = activity.getString(title) val text = activity.getString(title)
return RemoteActionCompat( return RemoteActionCompat(
IconCompat.createWithResource(activity, id), IconCompat.createWithResource(activity, id),
text, text,
text, text,
getPendingIntent(activity, event.value) getPendingIntent(activity, event.value),
) )
} }
@ -411,35 +411,35 @@ object PlayerHelper {
activity, activity,
R.drawable.ic_headphones, R.drawable.ic_headphones,
R.string.background_mode, R.string.background_mode,
PlayerEvent.Background PlayerEvent.Background,
) )
val rewindAction = getRemoteAction( val rewindAction = getRemoteAction(
activity, activity,
R.drawable.ic_rewind, R.drawable.ic_rewind,
R.string.rewind, R.string.rewind,
PlayerEvent.Rewind PlayerEvent.Rewind,
) )
val playPauseAction = getRemoteAction( val playPauseAction = getRemoteAction(
activity, activity,
if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play, if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play,
R.string.pause, R.string.pause,
if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play if (isPlaying) PlayerEvent.Pause else PlayerEvent.Play,
) )
val skipNextAction = getRemoteAction( val skipNextAction = getRemoteAction(
activity, activity,
R.drawable.ic_next, R.drawable.ic_next,
R.string.play_next, R.string.play_next,
PlayerEvent.Next PlayerEvent.Next,
) )
val forwardAction = getRemoteAction( val forwardAction = getRemoteAction(
activity, activity,
R.drawable.ic_forward, R.drawable.ic_forward,
R.string.forward, R.string.forward,
PlayerEvent.Forward PlayerEvent.Forward,
) )
return if (alternativePiPControls) { return if (alternativePiPControls) {
listOf(audioModeAction, playPauseAction, skipNextAction) listOf(audioModeAction, playPauseAction, skipNextAction)
@ -469,7 +469,7 @@ object PlayerHelper {
1000 * 10, // exo default is 50s 1000 * 10, // exo default is 50s
bufferingGoal, bufferingGoal,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS, DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS,
) )
.build() .build()
} }
@ -494,7 +494,7 @@ object PlayerHelper {
fun ExoPlayer.checkForSegments( fun ExoPlayer.checkForSegments(
context: Context, context: Context,
segments: List<Segment>, segments: List<Segment>,
skipManually: Boolean = false skipManually: Boolean = false,
): Long? { ): Long? {
for (segment in segments) { for (segment in segments) {
val segmentStart = (segment.segment[0] * 1000f).toLong() val segmentStart = (segment.segment[0] * 1000f).toLong()

View File

@ -135,12 +135,12 @@ object PreferenceHelper {
ignorableChannels.remove(channelId) ignorableChannels.remove(channelId)
} else { } else {
ignorableChannels.add( ignorableChannels.add(
channelId channelId,
) )
} }
editor.putString( editor.putString(
PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS, PreferenceKeys.IGNORED_NOTIFICATION_CHANNELS,
ignorableChannels.joinToString(",") ignorableChannels.joinToString(","),
).apply() ).apply()
} }

View File

@ -34,7 +34,7 @@ object ProxyHelper {
fun unwrapIfEnabled(url: String): String { fun unwrapIfEnabled(url: String): String {
if (!PreferenceHelper.getBoolean( if (!PreferenceHelper.getBoolean(
PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY, PreferenceKeys.DISABLE_VIDEO_IMAGE_PROXY,
false false,
) )
) { ) {
return url return url

View File

@ -15,7 +15,7 @@ object ShortcutHelper {
AppShortcut("home", R.string.startpage, R.drawable.ic_home), AppShortcut("home", R.string.startpage, R.drawable.ic_home),
AppShortcut("trends", R.string.trends, R.drawable.ic_trending), AppShortcut("trends", R.string.trends, R.drawable.ic_trending),
AppShortcut("subscriptions", R.string.subscriptions, R.drawable.ic_subscriptions), AppShortcut("subscriptions", R.string.subscriptions, R.drawable.ic_subscriptions),
AppShortcut("library", R.string.library, R.drawable.ic_library) AppShortcut("library", R.string.library, R.drawable.ic_library),
) )
private fun createShortcut(context: Context, appShortcut: AppShortcut): ShortcutInfoCompat { private fun createShortcut(context: Context, appShortcut: AppShortcut): ShortcutInfoCompat {
@ -26,7 +26,7 @@ object ShortcutHelper {
.setIcon(IconCompat.createWithResource(context, appShortcut.drawable)) .setIcon(IconCompat.createWithResource(context, appShortcut.drawable))
.setIntent( .setIntent(
Intent(Intent.ACTION_VIEW, null, context, MainActivity::class.java) Intent(Intent.ACTION_VIEW, null, context, MainActivity::class.java)
.putExtra(IntentData.fragmentToOpen, appShortcut.action) .putExtra(IntentData.fragmentToOpen, appShortcut.action),
) )
.build() .build()
} }

View File

@ -33,12 +33,12 @@ object ThemeHelper {
* Update the accent color of the app * Update the accent color of the app
*/ */
private fun updateAccentColor( private fun updateAccentColor(
activity: AppCompatActivity activity: AppCompatActivity,
) { ) {
val theme = when ( val theme = when (
PreferenceHelper.getString( PreferenceHelper.getString(
PreferenceKeys.ACCENT_COLOR, PreferenceKeys.ACCENT_COLOR,
"purple" "purple",
) )
) { ) {
// set the accent color, use the pure black/white theme if enabled // set the accent color, use the pure black/white theme if enabled
@ -62,7 +62,7 @@ object ThemeHelper {
if ( if (
PreferenceHelper.getString( PreferenceHelper.getString(
PreferenceKeys.ACCENT_COLOR, PreferenceKeys.ACCENT_COLOR,
"purple" "purple",
) == "my" ) == "my"
) { ) {
DynamicColors.applyToActivityIfAvailable(activity) DynamicColors.applyToActivityIfAvailable(activity)
@ -75,7 +75,7 @@ object ThemeHelper {
private fun applyPureThemeIfEnabled(activity: Activity) { private fun applyPureThemeIfEnabled(activity: Activity) {
val pureThemeEnabled = PreferenceHelper.getBoolean( val pureThemeEnabled = PreferenceHelper.getBoolean(
PreferenceKeys.PURE_THEME, PreferenceKeys.PURE_THEME,
false false,
) )
if (pureThemeEnabled) activity.theme.applyStyle(R.style.Pure, true) if (pureThemeEnabled) activity.theme.applyStyle(R.style.Pure, true)
} }
@ -105,7 +105,7 @@ object ThemeHelper {
context.packageManager.setComponentEnabledSetting( context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, activityClass), ComponentName(context.packageName, activityClass),
PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP PackageManager.DONT_KILL_APP,
) )
} }
@ -115,7 +115,7 @@ object ThemeHelper {
context.packageManager.setComponentEnabledSetting( context.packageManager.setComponentEnabledSetting(
ComponentName(context.packageName, newLogoActivityClass), ComponentName(context.packageName, newLogoActivityClass),
PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP PackageManager.DONT_KILL_APP,
) )
} }

View File

@ -32,7 +32,7 @@ object WindowHelper {
// See: https://developer.android.com/training/system-ui/immersive#kotlin // See: https://developer.android.com/training/system-ui/immersive#kotlin
activity.toggleSystemBars( activity.toggleSystemBars(
types = WindowInsetsCompat.Type.systemBars(), types = WindowInsetsCompat.Type.systemBars(),
showBars = !isFullscreen showBars = !isFullscreen,
) )
} }
} }

View File

@ -6,5 +6,5 @@ import androidx.annotation.StringRes
data class AppShortcut( data class AppShortcut(
val action: String, val action: String,
@StringRes val label: Int, @StringRes val label: Int,
@DrawableRes val drawable: Int @DrawableRes val drawable: Int,
) )

View File

@ -20,5 +20,5 @@ data class BackupFile(
var playlistBookmarks: List<PlaylistBookmark>? = emptyList(), var playlistBookmarks: List<PlaylistBookmark>? = emptyList(),
var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(), var localPlaylists: List<LocalPlaylistWithVideos>? = emptyList(),
var preferences: List<PreferenceItem>? = emptyList(), var preferences: List<PreferenceItem>? = emptyList(),
var channelGroups: List<SubscriptionGroup>? = emptyList() var channelGroups: List<SubscriptionGroup>? = emptyList(),
) )

View File

@ -4,5 +4,5 @@ data class BottomSheetItem(
val title: String, val title: String,
val drawable: Int? = null, val drawable: Int? = null,
val getCurrent: () -> String? = { null }, val getCurrent: () -> String? = { null },
val onClick: () -> Unit = {} val onClick: () -> Unit = {},
) )

View File

@ -5,7 +5,7 @@ import com.github.libretube.R
sealed class ChannelTabs( sealed class ChannelTabs(
val identifierName: String, val identifierName: String,
@IdRes val chipId: Int @IdRes val chipId: Int,
) { ) {
object Playlists : ChannelTabs("playlists", R.id.playlists) object Playlists : ChannelTabs("playlists", R.id.playlists)
object Shorts : ChannelTabs("shorts", R.id.shorts) object Shorts : ChannelTabs("shorts", R.id.shorts)

View File

@ -2,5 +2,5 @@ package com.github.libretube.obj
data class Country( data class Country(
val name: String, val name: String,
val code: String val code: String,
) )

View File

@ -9,7 +9,7 @@ sealed class DownloadStatus {
data class Progress( data class Progress(
val progress: Long, val progress: Long,
val downloaded: Long, val downloaded: Long,
val total: Long val total: Long,
) : DownloadStatus() ) : DownloadStatus()
data class Error(val message: String, val cause: Throwable? = null) : DownloadStatus() data class Error(val message: String, val cause: Throwable? = null) : DownloadStatus()

View File

@ -7,5 +7,5 @@ data class DownloadedFile(
val name: String, val name: String,
val size: Long, val size: Long,
var metadata: Streams? = null, var metadata: Streams? = null,
var thumbnail: Bitmap? = null var thumbnail: Bitmap? = null,
) )

View File

@ -7,5 +7,5 @@ data class ImportPlaylist(
var name: String? = null, var name: String? = null,
val type: String? = null, val type: String? = null,
val visibility: String? = null, val visibility: String? = null,
var videos: List<String> = listOf() var videos: List<String> = listOf(),
) )

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class ImportPlaylistFile( data class ImportPlaylistFile(
val format: String, val format: String,
val version: Int, val version: Int,
val playlists: List<ImportPlaylist> = emptyList() val playlists: List<ImportPlaylist> = emptyList(),
) )

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
data class NewPipeSubscription( data class NewPipeSubscription(
val name: String, val name: String,
@SerialName("service_id") val serviceId: Int, @SerialName("service_id") val serviceId: Int,
val url: String val url: String,
) )

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.Serializable
data class NewPipeSubscriptions( data class NewPipeSubscriptions(
@SerialName("app_version") val appVersion: String = "", @SerialName("app_version") val appVersion: String = "",
@SerialName("app_version_int") val appVersionInt: Int = 0, @SerialName("app_version_int") val appVersionInt: Int = 0,
val subscriptions: List<NewPipeSubscription> = emptyList() val subscriptions: List<NewPipeSubscription> = emptyList(),
) )

View File

@ -6,5 +6,5 @@ data class PlayerNotificationData(
val title: String? = null, val title: String? = null,
val uploaderName: String? = null, val uploaderName: String? = null,
val thumbnailUrl: String? = null, val thumbnailUrl: String? = null,
val thumbnailPath: Path? = null val thumbnailPath: Path? = null,
) )

View File

@ -7,5 +7,5 @@ import kotlinx.serialization.json.JsonPrimitive
@Serializable @Serializable
data class PreferenceItem( data class PreferenceItem(
val key: String? = null, val key: String? = null,
val value: JsonPrimitive = JsonNull val value: JsonPrimitive = JsonNull,
) )

View File

@ -5,5 +5,5 @@ data class PreviewFrame(
val positionX: Int, val positionX: Int,
val positionY: Int, val positionY: Int,
val framesPerPageX: Int, val framesPerPageX: Int,
val framesPerPageY: Int val framesPerPageY: Int,
) )

View File

@ -4,5 +4,5 @@ data class ShareData(
val currentChannel: String? = null, val currentChannel: String? = null,
val currentVideo: String? = null, val currentVideo: String? = null,
val currentPlaylist: String? = null, val currentPlaylist: String? = null,
var currentPosition: Long? = null var currentPosition: Long? = null,
) )

View File

@ -2,5 +2,5 @@ package com.github.libretube.obj
data class VideoResolution( data class VideoResolution(
val name: String, val name: String,
val resolution: Int val resolution: Int,
) )

View File

@ -20,5 +20,5 @@ data class Asset(
val state: String, val state: String,
@SerialName("updated_at") val updatedAt: Instant, @SerialName("updated_at") val updatedAt: Instant,
val uploader: User, val uploader: User,
val url: String val url: String,
) )

View File

@ -12,5 +12,5 @@ data class Reactions(
val laugh: Int, val laugh: Int,
val rocket: Int, val rocket: Int,
@SerialName("total_count") val totalCount: Int, @SerialName("total_count") val totalCount: Int,
val url: String val url: String,
) )

Some files were not shown because too many files have changed in this diff Show More