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 json import load
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(), [], []
for file in files:
@ -15,7 +20,7 @@ if len(signed_files):
if tgconfig.GH_REPO.lower() == "libre-tube/libretube":
run("git add -f *")
run('git commit -m "WORKFLOW: ALPHA BUILDS"')
run(f"git commit -m \"{message}\"")
run("git push -u")
else:
print("Official Repo not Detected")

View File

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

View File

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

View File

@ -1,12 +1,17 @@
<div align="center">
<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)
</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)
[![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)
[![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;">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,5 +13,5 @@ data class Channel(
val subscriberCount: Long = 0,
val verified: Boolean = false,
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
data class ChannelTab(
val name: String,
val data: String
val data: String,
)

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class ChannelTabResponse(
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(
val title: String,
val image: String,
val start: Long
val start: Long,
)

View File

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

View File

@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
data class CommentsPage(
var comments: List<Comment> = emptyList(),
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 subscribers: 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,
val cdn: Boolean = false,
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
data class Login(
val username: String,
val password: String
val password: String,
)

View File

@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable
@Serializable
data class Message(
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(
val donationUrl: 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 fps: Int? = null,
val audioTrackName: String? = null,
val audioTrackId: String? = null
val audioTrackId: String? = null,
) {
fun getQualityString(fileName: String): String {
return "${fileName}_${quality?.replace(" ", "_")}_$format." +

View File

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

View File

@ -8,5 +8,5 @@ data class PlaylistId(
val videoId: String? = null,
val videoIds: List<String> = emptyList(),
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,
val shortDescription: 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 durationPerFrame: 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 nextpage: 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 userID: String? = 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(
val hash: String? = null,
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 uploaded: Long? = null,
val shortDescription: String? = null,
val isShort: Boolean = false
val isShort: Boolean = false,
) {
fun toLocalPlaylistItem(playlistId: String): LocalPlaylistItem {
return LocalPlaylistItem(
@ -31,7 +31,7 @@ data class StreamItem(
uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar,
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.enums.FileType
import com.github.libretube.helpers.ProxyHelper
import java.nio.file.Paths
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
import java.nio.file.Paths
@Serializable
data class Streams(
@ -33,7 +33,7 @@ data class Streams(
val proxyUrl: String? = null,
val chapters: List<ChapterSegment> = emptyList(),
val uploaderSubscriberCount: Long = 0,
val previewFrames: List<PreviewFrames> = emptyList()
val previewFrames: List<PreviewFrames> = emptyList(),
) {
fun toDownloadItems(
videoId: String,
@ -42,7 +42,7 @@ data class Streams(
videoQuality: String?,
audioFormat: String?,
audioQuality: String?,
subtitleCode: String?
subtitleCode: String?,
): List<DownloadItem> {
val items = mutableListOf<DownloadItem>()
@ -58,8 +58,8 @@ data class Streams(
path = Paths.get(""),
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
format = videoFormat,
quality = videoQuality
)
quality = videoQuality,
),
)
}
@ -75,8 +75,8 @@ data class Streams(
path = Paths.get(""),
url = stream?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
format = audioFormat,
quality = audioQuality
)
quality = audioQuality,
),
)
}
@ -89,8 +89,8 @@ data class Streams(
path = Paths.get(""),
url = subtitles.find {
it.code == subtitleCode
}?.url?.let { ProxyHelper.unwrapIfEnabled(it) }
)
}?.url?.let { ProxyHelper.unwrapIfEnabled(it) },
),
)
}
@ -110,7 +110,7 @@ data class Streams(
duration = duration,
views = views,
uploaderVerified = uploaderVerified,
shortDescription = description
shortDescription = description,
)
}
}

View File

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

View File

@ -8,5 +8,5 @@ data class Subtitle(
val mimeType: String? = null,
val name: 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
data class Token(
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 subtitle: CharSequence?,
private val aspectRatio: Rational?,
private val expandedAspectRatio: Rational?
private val expandedAspectRatio: Rational?,
) {
@RequiresApi(Build.VERSION_CODES.O)
fun toPictureInPictureParams(): PictureInPictureParams {
@ -109,7 +109,7 @@ class PictureInPictureParamsCompat private constructor(
title,
subtitle,
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 MASTODON_URL = "https://fosstodon.org/@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

View File

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

View File

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

View File

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

View File

@ -22,7 +22,7 @@ object DatabaseHelper {
streams.uploaderUrl.toID(),
streams.uploaderAvatar,
streams.thumbnailUrl,
streams.duration
streams.duration,
)
Database.watchHistoryDao().insert(watchHistoryItem)
val maxHistorySize = PreferenceHelper.getString(PreferenceKeys.WATCH_HISTORY_SIZE, "100")
@ -40,10 +40,14 @@ object DatabaseHelper {
suspend fun addToSearchHistory(searchHistoryItem: 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
val searchHistory = Database.searchHistoryDao().getAll()
if (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
val searchHistory = Database.searchHistoryDao().getAll().toMutableList()
while (searchHistory.size > MAX_SEARCH_HISTORY_SIZE) {
Database.searchHistoryDao().delete(searchHistory.first())
searchHistory.removeFirst()
}
}
}

View File

@ -10,5 +10,5 @@ import kotlinx.serialization.Serializable
class CustomInstance(
@PrimaryKey var name: 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.PrimaryKey
import java.nio.file.Path
import kotlinx.datetime.LocalDate
import java.nio.file.Path
@Entity(tableName = "download")
data class Download(
@ -13,5 +13,5 @@ data class Download(
val description: String = "",
val uploader: String = "",
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,
parentColumns = ["videoId"],
childColumns = ["videoId"],
onDelete = ForeignKey.CASCADE
)
]
onDelete = ForeignKey.CASCADE,
),
],
)
data class DownloadItem(
@PrimaryKey(autoGenerate = true)
@ -29,5 +29,5 @@ data class DownloadItem(
var url: String? = null,
var format: 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,
@Relation(
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)
val id: Int = 0,
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 uploaderAvatar: String? = null,
@ColumnInfo val thumbnailUrl: String? = null,
@ColumnInfo val duration: Long? = null
@ColumnInfo val duration: Long? = null,
) {
fun toStreamItem(): StreamItem {
return StreamItem(
@ -31,7 +31,7 @@ data class LocalPlaylistItem(
uploaderAvatar = ProxyHelper.rewriteUrl(uploaderAvatar),
uploadedDate = uploadDate,
uploaded = null,
duration = duration
duration = duration,
)
}
}

View File

@ -9,7 +9,7 @@ data class LocalPlaylistWithVideos(
@Embedded val playlist: LocalPlaylist = LocalPlaylist(),
@Relation(
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
@Entity(tableName = "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 uploader: 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
@Entity(tableName = "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")
data class SubscriptionGroup(
@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 var uploaderAvatar: 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")
data class WatchPosition(
@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 {
BEST,
WORST
WORST,
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class BrightnessHelper(private val activity: Activity) {
value: Float,
maxValue: Float,
minValue: Float = 0.0f,
shouldSave: Boolean = false
shouldSave: Boolean = false,
) {
brightness = value.normalize(minValue, maxValue, minBrightness, maxBrightness)
if (shouldSave) savedBrightness = brightness
@ -69,7 +69,7 @@ class BrightnessHelper(private val activity: Activity) {
fun getBrightnessWithScale(
maxValue: Float,
minValue: Float = 0.0f,
saved: Boolean = false
saved: Boolean = false,
): Float {
return if (saved) {
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.Streams
import org.w3c.dom.Document
import org.w3c.dom.Element
import java.io.StringWriter
import javax.xml.parsers.DocumentBuilder
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
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
@ -21,10 +21,10 @@ object DashHelper {
private data class AdapSetInfo(
val mimeType: String,
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 doc = builder.newDocument()
@ -40,17 +40,18 @@ object DashHelper {
val adapSetInfos = ArrayList<AdapSetInfo>()
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
.filter { !it.format.orEmpty().contains("HLS") }
// filter the codecs according to the user's preferences
.filter {
if (enabledVideoCodecs != "all") {
it.codec?.lowercase()?.startsWith(enabledVideoCodecs) ?: true
} else {
true
}
}) {
enabledVideoCodecs == "all" || it.codec.orEmpty().lowercase().startsWith(
enabledVideoCodecs,
)
}
.filter { supportsHdr || !it.quality.orEmpty().uppercase().contains("HDR") }
) {
// ignore dual format streams
if (!stream.videoOnly!!) {
continue
@ -70,8 +71,8 @@ object DashHelper {
AdapSetInfo(
stream.mimeType!!,
null,
mutableListOf(stream)
)
mutableListOf(stream),
),
)
}
@ -88,8 +89,8 @@ object DashHelper {
AdapSetInfo(
stream.mimeType!!,
stream.audioTrackId,
mutableListOf(stream)
)
mutableListOf(stream),
),
)
}
@ -144,7 +145,7 @@ object DashHelper {
val audioChannelConfiguration = doc.createElement("AudioChannelConfiguration")
audioChannelConfiguration.setAttribute(
"schemeIdUri",
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011"
"urn:mpeg:dash:23003:3:audio_channel_configuration:2011",
)
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 {
return PreferenceHelper.getString(
PreferenceKeys.MAX_CONCURRENT_DOWNLOADS,
"6"
"6",
).toFloat().toInt()
}
@ -56,7 +56,7 @@ object DownloadHelper {
videoQuality: String? = null,
audioFormat: String? = null,
audioQuality: String? = null,
subtitleCode: String? = null
subtitleCode: String? = null,
) {
val intent = Intent(context, DownloadService::class.java)

View File

@ -28,7 +28,7 @@ object ImageHelper {
fun initializeImageLoader(context: Context) {
val maxImageCacheSize = PreferenceHelper.getString(
PreferenceKeys.MAX_IMAGE_CACHE,
""
"",
)
imageLoader = ImageLoader.Builder(context)
@ -42,7 +42,7 @@ object ImageHelper {
DiskCache.Builder()
.directory(context.cacheDir.resolve("coil"))
.maxSizeBytes(maxImageCacheSize.toInt() * 1024 * 1024L)
.build()
.build(),
)
}
}
@ -109,7 +109,7 @@ object ImageHelper {
(bitmap.width - newSize) / 2,
(bitmap.height - newSize) / 2,
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.NewPipeSubscription
import com.github.libretube.obj.NewPipeSubscriptions
import kotlin.streams.toList
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import kotlin.streams.toList
object ImportHelper {
/**
@ -44,6 +44,7 @@ object ImportHelper {
/**
* Get a list of channel IDs from a file [Uri]
*/
@OptIn(ExperimentalSerializationApi::class)
private fun getChannelsFromUri(activity: Activity, uri: Uri): List<String> {
return when (val fileType = activity.contentResolver.getType(uri)) {
"application/json", "application/*", "application/octet-stream" -> {
@ -72,6 +73,7 @@ object ImportHelper {
/**
* Write the text to the document
*/
@OptIn(ExperimentalSerializationApi::class)
suspend fun exportSubscriptions(activity: Activity, uri: Uri) {
val token = PreferenceHelper.getToken()
val subs = if (token.isNotEmpty()) {
@ -105,10 +107,14 @@ object ImportHelper {
activity.contentResolver.openInputStream(uri)?.use {
val lines = it.bufferedReader().use { reader -> reader.lines().toList() }
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 ->
line.split(",").firstOrNull()?.let { videoId ->
if (videoId.isNotBlank()) playlist.videos = playlist.videos + videoId
if (videoId.isNotBlank()) playlist.videos += videoId.trim()
}
}
importPlaylists.add(playlist)
@ -145,6 +151,7 @@ object ImportHelper {
/**
* Export Playlists
*/
@OptIn(ExperimentalSerializationApi::class)
suspend fun exportPlaylists(activity: Activity, uri: Uri) {
val playlists = PlaylistsHelper.exportPlaylists()
val playlistFile = ImportPlaylistFile("Piped", 1, playlists)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ object ShortcutHelper {
AppShortcut("home", R.string.startpage, R.drawable.ic_home),
AppShortcut("trends", R.string.trends, R.drawable.ic_trending),
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 {
@ -26,7 +26,7 @@ object ShortcutHelper {
.setIcon(IconCompat.createWithResource(context, appShortcut.drawable))
.setIntent(
Intent(Intent.ACTION_VIEW, null, context, MainActivity::class.java)
.putExtra(IntentData.fragmentToOpen, appShortcut.action)
.putExtra(IntentData.fragmentToOpen, appShortcut.action),
)
.build()
}

View File

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

View File

@ -6,5 +6,5 @@ import androidx.annotation.StringRes
data class AppShortcut(
val action: String,
@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 localPlaylists: List<LocalPlaylistWithVideos>? = 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 drawable: Int? = 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(
val identifierName: String,
@IdRes val chipId: Int
@IdRes val chipId: Int,
) {
object Playlists : ChannelTabs("playlists", R.id.playlists)
object Shorts : ChannelTabs("shorts", R.id.shorts)

View File

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

View File

@ -9,7 +9,7 @@ sealed class DownloadStatus {
data class Progress(
val progress: Long,
val downloaded: Long,
val total: Long
val total: Long,
) : 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 size: Long,
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,
val type: 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(
val format: String,
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(
val name: String,
@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(
@SerialName("app_version") val appVersion: String = "",
@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 uploaderName: 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
data class PreferenceItem(
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 positionY: Int,
val framesPerPageX: Int,
val framesPerPageY: Int
val framesPerPageY: Int,
)

View File

@ -4,5 +4,5 @@ data class ShareData(
val currentChannel: String? = null,
val currentVideo: 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(
val name: String,
val resolution: Int
val resolution: Int,
)

View File

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

View File

@ -12,5 +12,5 @@ data class Reactions(
val laugh: Int,
val rocket: 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