Merge pull request #2755 from Isira-Seneviratne/KotlinX_Serialization

Switch to Kotlinx Serialization.
This commit is contained in:
Bnyro 2023-01-21 18:59:56 +01:00 committed by GitHub
commit dd1821ada3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
81 changed files with 589 additions and 615 deletions

View File

@ -4,6 +4,7 @@ plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'kotlinx-serialization'
}
android {
@ -107,10 +108,11 @@ dependencies {
implementation libs.exoplayer.extension.mediasession
implementation libs.exoplayer.dash
/* Retrofit and Jackson */
/* Retrofit and Kotlinx Serialization */
implementation libs.square.retrofit
implementation libs.square.retrofit.converterJackson
implementation libs.jacksonAnnotations
implementation libs.kotlinx.serialization
implementation libs.kotlinx.datetime
implementation libs.kotlinx.serialization.retrofit
/* Cronet and Coil */
coreLibraryDesugaring libs.desugaring

View File

@ -29,3 +29,26 @@
# Keep data classes used for Retrofit
-keep class com.github.libretube.obj.** { *; }
-keep class com.github.libretube.obj.update.** { *; }
# Keep rules required by Kotlinx Serialization
-if @kotlinx.serialization.Serializable class **
-keepclassmembers class <1> {
static <1>$Companion Companion;
}
-if @kotlinx.serialization.Serializable class ** {
static **$* *;
}
-keepclassmembers class <2>$<3> {
kotlinx.serialization.KSerializer serializer(...);
}
-if @kotlinx.serialization.Serializable class ** {
public static ** INSTANCE;
}
-keepclassmembers class <1> {
public static <1> INSTANCE;
kotlinx.serialization.KSerializer serializer(...);
}
-keepattributes RuntimeVisibleAnnotations,AnnotationDefault

View File

@ -0,0 +1,9 @@
package com.github.libretube.api
import kotlinx.serialization.json.Json
object JsonHelper {
val json = Json {
ignoreUnknownKeys = true
}
}

View File

@ -15,6 +15,7 @@ import com.github.libretube.api.obj.SegmentData
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Streams
import com.github.libretube.api.obj.Subscribe
import com.github.libretube.api.obj.Subscribed
import com.github.libretube.api.obj.Subscription
import com.github.libretube.api.obj.Token
import retrofit2.http.Body
@ -114,7 +115,7 @@ interface PipedApi {
suspend fun isSubscribed(
@Query("channelId") channelId: String,
@Header("Authorization") token: String
): com.github.libretube.api.obj.Subscribed
): Subscribed
@GET("subscriptions")
suspend fun subscriptions(@Header("Authorization") token: String): List<Subscription>

View File

@ -93,10 +93,7 @@ object PlaylistsHelper {
}.last().playlist.id.toString()
}
val response = try {
RetrofitInstance.authApi.createPlaylist(
token,
Playlists(name = playlistName)
)
RetrofitInstance.authApi.createPlaylist(token, Playlists(name = playlistName))
} catch (e: IOException) {
appContext.toastFromMainThread(R.string.unknown_error)
return null
@ -107,7 +104,7 @@ object PlaylistsHelper {
}
if (response.playlistId != null) {
appContext.toastFromMainThread(R.string.playlistCreated)
return response.playlistId!!
return response.playlistId
}
return null
}
@ -141,13 +138,8 @@ object PlaylistsHelper {
return true
}
return RetrofitInstance.authApi.addToPlaylist(
token,
PlaylistId(
playlistId = playlistId,
videoIds = videos.toList().map { it.url!!.toID() }
)
).message == "ok"
val playlist = PlaylistId(playlistId, videoIds = videos.map { it.url!!.toID() })
return RetrofitInstance.authApi.addToPlaylist(token, playlist).message == "ok"
}
suspend fun renamePlaylist(playlistId: String, newName: String): Boolean {
@ -164,10 +156,7 @@ object PlaylistsHelper {
return RetrofitInstance.authApi.renamePlaylist(
token,
PlaylistId(
playlistId = playlistId,
newName = newName
)
PlaylistId(playlistId, newName = newName)
).playlistId != null
}
@ -251,8 +240,8 @@ object PlaylistsHelper {
name = list.name,
type = "playlist",
visibility = "private",
videos = list.relatedStreams.orEmpty().map {
YOUTUBE_FRONTEND_URL + "/watch?v=" + it.url!!.toID()
videos = list.relatedStreams.map {
"$YOUTUBE_FRONTEND_URL/watch?v=${it.url!!.toID()}"
}
)
)
@ -278,19 +267,13 @@ object PlaylistsHelper {
val newPlaylist = createPlaylist(playlist.name ?: "Unknown name", appContext)
newPlaylist ?: return@launch
addToPlaylist(
newPlaylist,
*playlist.relatedStreams.orEmpty().toTypedArray()
)
addToPlaylist(newPlaylist, *playlist.relatedStreams.toTypedArray())
var nextPage = playlist.nextpage
while (nextPage != null) {
nextPage = try {
RetrofitInstance.api.getPlaylistNextPage(playlistId, nextPage).apply {
addToPlaylist(
newPlaylist,
*relatedStreams.orEmpty().toTypedArray()
)
addToPlaylist(newPlaylist, *relatedStreams.toTypedArray())
}.nextpage
} catch (e: Exception) {
return@launch

View File

@ -3,40 +3,43 @@ package com.github.libretube.api
import com.github.libretube.constants.PIPED_API_URL
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.util.PreferenceHelper
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import okhttp3.MediaType.Companion.toMediaType
import retrofit2.Retrofit
import retrofit2.converter.jackson.JacksonConverterFactory
import retrofit2.create
object RetrofitInstance {
lateinit var url: String
lateinit var authUrl: String
val lazyMgr = resettableManager()
val jacksonConverterFactory = JacksonConverterFactory.create()
private val kotlinxConverterFactory = JsonHelper.json
.asConverterFactory("application/json".toMediaType())
val api: PipedApi by resettableLazy(lazyMgr) {
val api by resettableLazy(lazyMgr) {
Retrofit.Builder()
.baseUrl(url)
.callFactory(CronetHelper.callFactory)
.addConverterFactory(jacksonConverterFactory)
.addConverterFactory(kotlinxConverterFactory)
.build()
.create(PipedApi::class.java)
.create<PipedApi>()
}
val authApi: PipedApi by resettableLazy(lazyMgr) {
val authApi by resettableLazy(lazyMgr) {
Retrofit.Builder()
.baseUrl(authUrl)
.callFactory(CronetHelper.callFactory)
.addConverterFactory(jacksonConverterFactory)
.addConverterFactory(kotlinxConverterFactory)
.build()
.create(PipedApi::class.java)
.create<PipedApi>()
}
val externalApi: ExternalApi by resettableLazy(lazyMgr) {
val externalApi by resettableLazy(lazyMgr) {
Retrofit.Builder()
.baseUrl(url)
.callFactory(CronetHelper.callFactory)
.addConverterFactory(jacksonConverterFactory)
.addConverterFactory(kotlinxConverterFactory)
.build()
.create(ExternalApi::class.java)
.create<ExternalApi>()
}
/**

View File

@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import com.github.libretube.R
import com.github.libretube.api.obj.StreamItem
import com.github.libretube.api.obj.Subscribe
import com.github.libretube.api.obj.Subscription
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHolder.Companion.Database
@ -25,7 +26,7 @@ object SubscriptionHelper {
try {
RetrofitInstance.authApi.subscribe(
PreferenceHelper.getToken(),
com.github.libretube.api.obj.Subscribe(channelId)
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG(), e.toString())
@ -46,7 +47,7 @@ object SubscriptionHelper {
try {
RetrofitInstance.authApi.unsubscribe(
PreferenceHelper.getToken(),
com.github.libretube.api.obj.Subscribe(channelId)
Subscribe(channelId)
)
} catch (e: Exception) {
Log.e(TAG(), e.toString())

View File

@ -1,17 +1,17 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Channel(
var id: String? = null,
var name: String? = null,
var avatarUrl: String? = null,
var bannerUrl: String? = null,
var description: String? = null,
var nextpage: String? = null,
var subscriberCount: Long = 0,
var verified: Boolean = false,
var relatedStreams: List<StreamItem>? = listOf(),
var tabs: List<ChannelTab>? = listOf()
val id: String,
val name: String,
val avatarUrl: String,
val bannerUrl: String,
val description: String,
val nextpage: String? = null,
val subscriberCount: Long = 0,
val verified: Boolean = false,
val relatedStreams: List<StreamItem> = emptyList(),
val tabs: List<ChannelTab> = emptyList()
)

View File

@ -1,9 +1,9 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class ChannelTab(
val name: String? = null,
val data: String? = null
val name: String,
val data: String
)

View File

@ -1,6 +1,9 @@
package com.github.libretube.api.obj
import kotlinx.serialization.Serializable
@Serializable
data class ChannelTabResponse(
val content: List<ContentItem> = listOf(),
val content: List<ContentItem> = emptyList(),
val nextpage: String? = null
)

View File

@ -1,10 +1,10 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class ChapterSegment(
var title: String? = null,
var image: String? = null,
var start: Long? = null
val title: String? = null,
val image: String? = null,
val start: Long? = null
)

View File

@ -1,19 +1,19 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Comment(
val author: String? = null,
val commentId: String? = null,
val commentText: String? = null,
val commentedTime: String? = null,
val commentorUrl: String? = null,
val author: String,
val commentId: String,
val commentText: String,
val commentedTime: String,
val commentorUrl: String,
val repliesPage: String? = null,
val hearted: Boolean? = null,
val likeCount: Long? = null,
val pinned: Boolean? = null,
val thumbnail: String? = null,
val verified: Boolean? = null,
val replyCount: Long? = null
val hearted: Boolean,
val likeCount: Long,
val pinned: Boolean,
val thumbnail: String,
val verified: Boolean,
val replyCount: Long
)

View File

@ -1,10 +1,10 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class CommentsPage(
var comments: MutableList<Comment> = arrayListOf(),
val disabled: Boolean? = null,
var comments: List<Comment> = emptyList(),
val disabled: Boolean = false,
val nextpage: String? = null
)

View File

@ -1,28 +1,28 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class ContentItem(
var url: String? = null,
val type: String? = null,
var thumbnail: String? = null,
var uploaderName: String? = null,
var uploaded: Long? = null,
var shortDescription: String? = null,
val url: String,
val type: String,
val thumbnail: String,
val uploaderName: String? = null,
val uploaded: Long? = null,
val shortDescription: String? = null,
// Video only attributes
var title: String? = null,
var uploaderUrl: String? = null,
var uploaderAvatar: String? = null,
var uploadedDate: String? = null,
var duration: Long? = null,
var views: Long? = null,
var isShort: Boolean? = null,
var uploaderVerified: Boolean? = null,
val title: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val uploadedDate: String? = null,
val duration: Long = -1,
val views: Long = -1,
val isShort: Boolean? = null,
val uploaderVerified: Boolean? = null,
// Channel and Playlist attributes
var name: String? = null,
var description: String? = null,
var subscribers: Long? = -1,
var videos: Long? = -1,
var verified: Boolean? = null
val name: String? = null,
val description: String? = null,
val subscribers: Long = -1,
val videos: Long = -1,
val verified: Boolean? = null
)

View File

@ -1,5 +1,6 @@
package com.github.libretube.api.obj
data class DeleteUserRequest(
var password: String? = null
)
import kotlinx.serialization.Serializable
@Serializable
data class DeleteUserRequest(val password: String)

View File

@ -1,15 +1,16 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Instances(
var name: String? = null,
var api_url: String? = null,
var locations: String? = null,
var version: String? = null,
var up_to_date: Boolean? = null,
var cdn: Boolean? = null,
var registered: Long? = null,
var last_checked: Long? = null
val name: String,
@SerialName("api_url") val apiUrl: String,
val locations: String,
val version: String,
@SerialName("up_to_date") val upToDate: Boolean,
val cdn: Boolean,
val registered: Long,
@SerialName("last_checked") val lastChecked: Long
)

View File

@ -1,9 +1,9 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Login(
val username: String? = null,
val password: String? = null
val username: String,
val password: String
)

View File

@ -1,5 +1,6 @@
package com.github.libretube.api.obj
data class Message(
var message: String? = null
)
import kotlinx.serialization.Serializable
@Serializable
data class Message(val message: String? = null)

View File

@ -1,5 +1,8 @@
package com.github.libretube.api.obj
import kotlinx.serialization.Serializable
@Serializable
data class PipedConfig(
val donationUrl: String? = null,
val statusPageUrl: String? = null,

View File

@ -1,23 +1,23 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class PipedStream(
var url: String? = null,
var format: String? = null,
var quality: String? = null,
var mimeType: String? = null,
var codec: String? = null,
var videoOnly: Boolean? = null,
var bitrate: Int? = null,
var initStart: Int? = null,
var initEnd: Int? = null,
var indexStart: Int? = null,
var indexEnd: Int? = null,
var width: Int? = null,
var height: Int? = null,
var fps: Int? = null,
val url: String? = null,
val format: String? = null,
val quality: String? = null,
val mimeType: String? = null,
val codec: String? = null,
val videoOnly: Boolean? = null,
val bitrate: Int? = null,
val initStart: Int? = null,
val initEnd: Int? = null,
val indexStart: Int? = null,
val indexEnd: Int? = null,
val width: Int? = null,
val height: Int? = null,
val fps: Int? = null,
val audioTrackName: String? = null,
val audioTrackId: String? = null
)

View File

@ -1,16 +1,16 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Playlist(
var name: String? = null,
var thumbnailUrl: String? = null,
var bannerUrl: String? = null,
var nextpage: String? = null,
var uploader: String? = null,
var uploaderUrl: String? = null,
var uploaderAvatar: String? = null,
var videos: Int? = 0,
var relatedStreams: List<StreamItem>? = null
val name: String? = null,
val thumbnailUrl: String? = null,
val bannerUrl: String? = null,
val nextpage: String? = null,
val uploader: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val videos: Int = 0,
val relatedStreams: List<StreamItem> = emptyList()
)

View File

@ -1,12 +1,12 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class PlaylistId(
var playlistId: String? = null,
var videoId: String? = null,
var videoIds: List<String>? = null,
var newName: String? = null,
var index: Int = -1
val playlistId: String? = null,
val videoId: String? = null,
val videoIds: List<String> = emptyList(),
val newName: String? = null,
val index: Int = -1
)

View File

@ -1,12 +1,12 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Playlists(
var id: String? = null,
var name: String? = null,
var shortDescription: String? = null,
var thumbnail: String? = null,
var videos: Long? = null
val id: String? = null,
val name: String? = null,
val shortDescription: String? = null,
val thumbnail: String? = null,
val videos: Long = 0
)

View File

@ -1,8 +1,8 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class PreviewFrames(
val urls: List<String>? = null,
val frameWidth: Int? = null,

View File

@ -1,11 +1,11 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class SearchResult(
val items: MutableList<ContentItem>? = arrayListOf(),
val items: List<ContentItem> = emptyList(),
val nextpage: String? = null,
val suggestion: String? = "",
val suggestion: String? = null,
val corrected: Boolean? = null
)

View File

@ -1,8 +1,8 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Segment(
val UUID: String? = null,
val actionType: String? = null,

View File

@ -1,8 +1,8 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class SegmentData(
val hash: String? = null,
val segments: List<Segment> = listOf(),

View File

@ -1,21 +1,21 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class StreamItem(
var url: String? = null,
val url: String? = null,
val type: String? = null,
var title: String? = null,
var thumbnail: String? = null,
var uploaderName: String? = null,
var uploaderUrl: String? = null,
var uploaderAvatar: String? = null,
var uploadedDate: String? = null,
var duration: Long? = null,
var views: Long? = null,
var uploaderVerified: Boolean? = null,
var uploaded: Long? = null,
var shortDescription: String? = null,
val title: String? = null,
val thumbnail: String? = null,
val uploaderName: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val uploadedDate: String? = null,
val duration: Long? = null,
val views: Long? = null,
val uploaderVerified: Boolean? = null,
val uploaded: Long? = null,
val shortDescription: String? = null,
val isShort: Boolean = false
)

View File

@ -1,31 +1,32 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.datetime.LocalDate
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Streams(
val title: String? = null,
val description: String? = null,
val uploadDate: String? = null,
val uploader: String? = null,
val uploaderUrl: String? = null,
val uploaderAvatar: String? = null,
val thumbnailUrl: String? = null,
val title: String,
val description: String,
val uploadDate: LocalDate,
val uploader: String,
val uploaderUrl: String,
val uploaderAvatar: String,
val thumbnailUrl: String,
val hls: String? = null,
val dash: String? = null,
val lbryId: String? = null,
val uploaderVerified: Boolean? = null,
val duration: Long? = null,
val views: Long? = null,
val likes: Long? = null,
val dislikes: Long? = null,
val audioStreams: List<PipedStream>? = null,
val videoStreams: List<PipedStream>? = null,
val relatedStreams: List<StreamItem>? = null,
val subtitles: List<Subtitle>? = null,
val livestream: Boolean? = null,
val uploaderVerified: Boolean,
val duration: Long,
val views: Long = 0,
val likes: Long = 0,
val dislikes: Long = 0,
val audioStreams: List<PipedStream> = emptyList(),
val videoStreams: List<PipedStream> = emptyList(),
val relatedStreams: List<StreamItem> = emptyList(),
val subtitles: List<Subtitle> = emptyList(),
val livestream: Boolean = false,
val proxyUrl: String? = null,
val chapters: List<ChapterSegment>? = null,
val uploaderSubscriberCount: Long? = null,
val previewFrames: List<PreviewFrames>? = null
val chapters: List<ChapterSegment> = emptyList(),
val uploaderSubscriberCount: Long = 0,
val previewFrames: List<PreviewFrames> = emptyList()
)

View File

@ -1,8 +1,6 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
data class Subscribe(
var channelId: String? = null
)
@Serializable
data class Subscribe(val channelId: String)

View File

@ -1,8 +1,6 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
data class Subscribed(
var subscribed: Boolean? = null
)
@Serializable
data class Subscribed(val subscribed: Boolean? = null)

View File

@ -1,11 +1,11 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Subscription(
var url: String? = null,
var name: String? = null,
var avatar: String? = null,
var verified: Boolean? = null
val url: String,
val name: String,
val avatar: String,
val verified: Boolean
)

View File

@ -1,8 +1,8 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Subtitle(
val url: String? = null,
val mimeType: String? = null,

View File

@ -1,9 +1,9 @@
package com.github.libretube.api.obj
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Token(
var token: String? = null,
var error: String? = null
val token: String? = null,
val error: String? = null
)

View File

@ -16,9 +16,9 @@ object DatabaseHelper {
val watchHistoryItem = WatchHistoryItem(
videoId,
streams.title,
streams.uploadDate,
streams.uploadDate.toString(),
streams.uploader,
streams.uploaderUrl!!.toID(),
streams.uploaderUrl.toID(),
streams.uploaderAvatar,
streams.thumbnailUrl,
streams.duration

View File

@ -3,7 +3,9 @@ package com.github.libretube.db.obj
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "customInstance")
class CustomInstance(
@PrimaryKey var name: String = "",

View File

@ -2,7 +2,9 @@ package com.github.libretube.db.obj
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity
data class LocalPlaylist(
@PrimaryKey(autoGenerate = true)

View File

@ -3,7 +3,9 @@ package com.github.libretube.db.obj
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity
data class LocalPlaylistItem(
@PrimaryKey(autoGenerate = true) val id: Int = 0,

View File

@ -2,7 +2,9 @@ package com.github.libretube.db.obj
import androidx.room.Embedded
import androidx.room.Relation
import kotlinx.serialization.Serializable
@Serializable
data class LocalPlaylistWithVideos(
@Embedded val playlist: LocalPlaylist = LocalPlaylist(),
@Relation(

View File

@ -2,7 +2,9 @@ package com.github.libretube.db.obj
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "localSubscription")
data class LocalSubscription(
@PrimaryKey val channelId: String = ""

View File

@ -2,7 +2,9 @@ package com.github.libretube.db.obj
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "playlistBookmark")
data class PlaylistBookmark(
@PrimaryKey

View File

@ -2,7 +2,9 @@ package com.github.libretube.db.obj
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "searchHistoryItem")
data class SearchHistoryItem(
@PrimaryKey val query: String = ""

View File

@ -3,7 +3,9 @@ package com.github.libretube.db.obj
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "watchHistoryItem")
data class WatchHistoryItem(
@PrimaryKey val videoId: String = "",

View File

@ -3,7 +3,9 @@ package com.github.libretube.db.obj
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.serialization.Serializable
@Serializable
@Entity(tableName = "watchPosition")
data class WatchPosition(
@PrimaryKey val videoId: String = "",

View File

@ -13,7 +13,7 @@ fun Streams.toStreamItem(videoId: String): StreamItem {
uploaderName = uploader,
uploaderUrl = uploaderUrl,
uploaderAvatar = uploaderAvatar,
uploadedDate = uploadDate,
uploadedDate = uploadDate.toString(),
uploaded = null,
duration = duration,
views = views,

View File

@ -7,14 +7,16 @@ import com.github.libretube.db.obj.PlaylistBookmark
import com.github.libretube.db.obj.SearchHistoryItem
import com.github.libretube.db.obj.WatchHistoryItem
import com.github.libretube.db.obj.WatchPosition
import kotlinx.serialization.Serializable
@Serializable
data class BackupFile(
var watchHistory: List<WatchHistoryItem>? = null,
var watchPositions: List<WatchPosition>? = null,
var searchHistory: List<SearchHistoryItem>? = null,
var localSubscriptions: List<LocalSubscription>? = null,
var customInstances: List<CustomInstance>? = null,
var playlistBookmarks: List<PlaylistBookmark>? = null,
var localPlaylists: List<LocalPlaylistWithVideos>? = null,
var preferences: List<PreferenceItem>? = null
var watchHistory: List<WatchHistoryItem> = emptyList(),
var watchPositions: List<WatchPosition> = emptyList(),
var searchHistory: List<SearchHistoryItem> = emptyList(),
var localSubscriptions: List<LocalSubscription> = emptyList(),
var customInstances: List<CustomInstance> = emptyList(),
var playlistBookmarks: List<PlaylistBookmark> = emptyList(),
var localPlaylists: List<LocalPlaylistWithVideos> = emptyList(),
var preferences: List<PreferenceItem> = emptyList()
)

View File

@ -1,5 +1,8 @@
package com.github.libretube.obj
import kotlinx.serialization.Serializable
@Serializable
data class ImportPlaylist(
var name: String? = null,
val type: String? = null,

View File

@ -1,7 +1,10 @@
package com.github.libretube.obj
import kotlinx.serialization.Serializable
@Serializable
data class ImportPlaylistFile(
val format: String? = null,
val version: Int? = null,
val playlists: List<ImportPlaylist>? = null
val format: String,
val version: Int,
val playlists: List<ImportPlaylist> = emptyList()
)

View File

@ -1,7 +1,11 @@
package com.github.libretube.obj
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class NewPipeSubscription(
val name: String? = null,
val service_id: Int? = null,
val url: String? = null
val name: String,
@SerialName("service_id") val serviceId: Int,
val url: String
)

View File

@ -1,7 +1,11 @@
package com.github.libretube.obj
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class NewPipeSubscriptions(
val app_version: String = "",
val app_version_int: Int = 0,
val subscriptions: List<NewPipeSubscription>? = null
@SerialName("app_version") val appVersion: String = "",
@SerialName("app_version_int") val appVersionInt: Int = 0,
val subscriptions: List<NewPipeSubscription> = emptyList()
)

View File

@ -1,6 +1,11 @@
package com.github.libretube.obj
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class PreferenceItem(
val key: String? = null,
val value: Any? = null
val value: JsonPrimitive = JsonNull
)

View File

@ -1,20 +1,24 @@
package com.github.libretube.obj.update
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Asset(
val browser_download_url: String? = null,
val content_type: String? = null,
val created_at: String? = null,
val download_count: Int? = null,
val id: Int? = null,
val label: Any? = null,
val name: String? = null,
val node_id: String? = null,
val size: Int? = null,
val state: String? = null,
val updated_at: String? = null,
val uploader: Uploader? = null,
val url: String? = null
@SerialName("browser_download_url") val browserDownloadUrl: String,
@SerialName("content_type") val contentType: String,
@SerialName("created_at") val createdAt: Instant,
@SerialName("download_count") val downloadCount: Int,
val id: Int,
val label: JsonElement = JsonNull,
val name: String,
@SerialName("node_id") val nodeId: String,
val size: Int,
val state: String,
@SerialName("updated_at") val updatedAt: Instant,
val uploader: User,
val url: String
)

View File

@ -1,25 +0,0 @@
package com.github.libretube.obj.update
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class Author(
val avatar_url: String? = null,
val events_url: String? = null,
val followers_url: String? = null,
val following_url: String? = null,
val gists_url: String? = null,
val gravatar_id: String? = null,
val html_url: String? = null,
val id: Int? = null,
val login: String? = null,
val node_id: String? = null,
val organizations_url: String? = null,
val received_events_url: String? = null,
val repos_url: String? = null,
val site_admin: Boolean? = null,
val starred_url: String? = null,
val subscriptions_url: String? = null,
val type: String? = null,
val url: String? = null
)

View File

@ -1,15 +1,16 @@
package com.github.libretube.obj.update
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class Reactions(
val confused: Int? = null,
val eyes: Int? = null,
val heart: Int? = null,
val hooray: Int? = null,
val laugh: Int? = null,
val rocket: Int? = null,
val total_count: Int? = null,
val url: String? = null
val confused: Int,
val eyes: Int,
val heart: Int,
val hooray: Int,
val laugh: Int,
val rocket: Int,
@SerialName("total_count") val totalCount: Int,
val url: String
)

View File

@ -1,27 +1,29 @@
package com.github.libretube.obj.update
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import kotlinx.datetime.Instant
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@JsonIgnoreProperties(ignoreUnknown = true)
@Serializable
data class UpdateInfo(
val assets: List<Asset>? = null,
val assets_url: String? = null,
val author: Author? = null,
val body: String? = null,
val created_at: String? = null,
val draft: Boolean? = null,
val html_url: String? = null,
val id: Int? = null,
val mentions_count: Int? = null,
val name: String? = null,
val node_id: String? = null,
val prerelease: Boolean? = null,
val published_at: String? = null,
val reactions: Reactions? = null,
val tag_name: String? = null,
val tarball_url: String? = null,
val target_commitish: String? = null,
val upload_url: String? = null,
val url: String? = null,
val zipball_url: String? = null
val assets: List<Asset> = emptyList(),
@SerialName("assets_url") val assetsUrl: String,
val author: User,
val body: String,
@SerialName("created_at") val createdAt: Instant,
val draft: Boolean,
@SerialName("html_url") val htmlUrl: String,
val id: Int,
@SerialName("mentions_count") val mentionsCount: Int,
val name: String,
@SerialName("node_id") val nodeId: String,
val prerelease: Boolean,
@SerialName("published_at") val publishedAt: Instant,
val reactions: Reactions,
@SerialName("tag_name") val tagName: String,
@SerialName("tarball_url") val tarballUrl: String,
@SerialName("target_commitish") val targetCommitish: String,
@SerialName("upload_url") val uploadUrl: String,
val url: String,
@SerialName("zipball_url") val zipballUrl: String
)

View File

@ -1,25 +0,0 @@
package com.github.libretube.obj.update
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class Uploader(
val avatar_url: String? = null,
val events_url: String? = null,
val followers_url: String? = null,
val following_url: String? = null,
val gists_url: String? = null,
val gravatar_id: String? = null,
val html_url: String? = null,
val id: Int? = null,
val login: String? = null,
val node_id: String? = null,
val organizations_url: String? = null,
val received_events_url: String? = null,
val repos_url: String? = null,
val site_admin: Boolean? = null,
val starred_url: String? = null,
val subscriptions_url: String? = null,
val type: String? = null,
val url: String? = null
)

View File

@ -0,0 +1,26 @@
package com.github.libretube.obj.update
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
data class User(
@SerialName("avatar_url") val avatarUrl: String,
@SerialName("events_url") val eventsUrl: String,
@SerialName("followers_url") val followersUrl: String,
@SerialName("following_url") val followingUrl: String,
@SerialName("gists_url") val gistsUrl: String,
@SerialName("gravatar_id") val gravatarId: String,
@SerialName("html_url") val htmlUrl: String,
val id: Int,
val login: String,
@SerialName("node_id") val nodeId: String,
@SerialName("organizations_url") val organizationsUrl: String,
@SerialName("received_events_url") val receivedEventsUrl: String,
@SerialName("repos_url") val reposUrl: String,
@SerialName("site_admin") val siteAdmin: Boolean,
@SerialName("starred_url") val starredUrl: String,
@SerialName("subscriptions_url") val subscriptionsUrl: String,
val type: String,
val url: String
)

View File

@ -13,8 +13,8 @@ import android.os.Looper
import android.util.Log
import android.widget.Toast
import androidx.core.app.ServiceCompat
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.Segment
import com.github.libretube.api.obj.SegmentData
@ -40,6 +40,7 @@ import com.google.android.exoplayer2.Player
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
/**
* Loads the selected videos audio in background mode with a notification area.
@ -318,11 +319,10 @@ class BackgroundMode : Service() {
runCatching {
val categories = PlayerHelper.getSponsorBlockCategories()
if (categories.isEmpty()) return@runCatching
segmentData =
RetrofitInstance.api.getSegments(
videoId,
ObjectMapper().writeValueAsString(categories)
)
segmentData = RetrofitInstance.api.getSegments(
videoId,
JsonHelper.json.encodeToString(categories)
)
checkForSegments()
}
}

View File

@ -102,21 +102,19 @@ class DownloadService : Service() {
Database.downloadDao().insertDownload(
Download(
videoId = videoId,
title = streams.title ?: "",
title = streams.title,
thumbnailPath = thumbnailTargetFile.absolutePath,
description = streams.description ?: "",
uploadDate = streams.uploadDate,
uploader = streams.uploader ?: ""
description = streams.description,
uploadDate = streams.uploadDate.toString(),
uploader = streams.uploader
)
)
}
streams.thumbnailUrl?.let { url ->
ImageHelper.downloadImage(
this@DownloadService,
url,
thumbnailTargetFile.absolutePath
)
}
ImageHelper.downloadImage(
this@DownloadService,
streams.thumbnailUrl,
thumbnailTargetFile.absolutePath
)
val downloadItems = streams.toDownloadItems(
videoId,

View File

@ -66,22 +66,21 @@ class CommentsAdapter(
commentorImage.scaleY = REPLIES_ADAPTER_SCALE
}
commentInfos.text =
comment.author.toString() + TextUtils.SEPARATOR + comment.commentedTime.toString()
commentInfos.text = comment.author + TextUtils.SEPARATOR + comment.commentedTime
commentText.text = HtmlCompat.fromHtml(
comment.commentText.toString(),
comment.commentText,
HtmlCompat.FROM_HTML_MODE_LEGACY
)
ImageHelper.loadImage(comment.thumbnail, commentorImage)
likesTextView.text = comment.likeCount.formatShort()
if (comment.verified == true) verifiedImageView.visibility = View.VISIBLE
if (comment.pinned == true) pinnedImageView.visibility = View.VISIBLE
if (comment.hearted == true) heartedImageView.visibility = View.VISIBLE
if (comment.verified) verifiedImageView.visibility = View.VISIBLE
if (comment.pinned) pinnedImageView.visibility = View.VISIBLE
if (comment.hearted) heartedImageView.visibility = View.VISIBLE
if (comment.repliesPage != null) repliesAvailable.visibility = View.VISIBLE
if ((comment.replyCount ?: -1L) > 0L) {
repliesCount.text = comment.replyCount?.formatShort()
if (comment.replyCount > 0L) {
repliesCount.text = comment.replyCount.formatShort()
}
commentorImage.setOnClickListener {
@ -99,7 +98,7 @@ class CommentsAdapter(
}
root.setOnLongClickListener {
ClipboardHelper(root.context).save(comment.commentText.toString())
ClipboardHelper(root.context).save(comment.commentText)
Toast.makeText(root.context, R.string.copied, Toast.LENGTH_SHORT).show()
true
}

View File

@ -83,13 +83,13 @@ class SearchAdapter(
private fun bindWatch(item: ContentItem, binding: VideoRowBinding) {
binding.apply {
ImageHelper.loadImage(item.thumbnail, thumbnail)
thumbnailDuration.setFormattedDuration(item.duration!!, item.isShort)
thumbnailDuration.setFormattedDuration(item.duration, item.isShort)
ImageHelper.loadImage(item.uploaderAvatar, channelImage)
videoTitle.text = item.title
val viewsString = if (item.views?.toInt() != -1) item.views.formatShort() else ""
val uploadDate = if (item.uploadedDate != null) item.uploadedDate else ""
val viewsString = if (item.views != -1L) item.views.formatShort() else ""
val uploadDate = item.uploadedDate.orEmpty()
videoInfo.text =
if (viewsString != "" && uploadDate != "") {
if (viewsString.isNotEmpty() && uploadDate.isNotEmpty()) {
"$viewsString$uploadDate"
} else {
viewsString + uploadDate
@ -98,7 +98,7 @@ class SearchAdapter(
root.setOnClickListener {
NavigationHelper.navigateVideo(root.context, item.url)
}
val videoId = item.url!!.toID()
val videoId = item.url.toID()
val videoName = item.title!!
root.setOnLongClickListener {
VideoOptionsBottomSheet(videoId, videoName)
@ -111,7 +111,7 @@ class SearchAdapter(
channelContainer.setOnClickListener {
NavigationHelper.navigateChannel(root.context, item.uploaderUrl)
}
watchProgress.setWatchProgressLength(videoId, item.duration!!)
watchProgress.setWatchProgressLength(videoId, item.duration)
}
}
@ -135,12 +135,12 @@ class SearchAdapter(
}
root.setOnLongClickListener {
ChannelOptionsBottomSheet(item.url!!.toID(), item.name)
ChannelOptionsBottomSheet(item.url.toID(), item.name)
.show((root.context as BaseActivity).supportFragmentManager)
true
}
binding.searchSubButton.setupSubscriptionButton(item.url?.toID(), item.name?.toID())
binding.searchSubButton.setupSubscriptionButton(item.url.toID(), item.name?.toID())
}
}
@ -150,7 +150,7 @@ class SearchAdapter(
) {
binding.apply {
ImageHelper.loadImage(item.thumbnail, playlistThumbnail)
if (item.videos?.toInt() != -1) videoCount.text = item.videos.toString()
if (item.videos != -1L) videoCount.text = item.videos.toString()
playlistTitle.text = item.name
playlistDescription.text = item.uploaderName
root.setOnClickListener {
@ -158,7 +158,7 @@ class SearchAdapter(
}
deletePlaylist.visibility = View.GONE
root.setOnLongClickListener {
val playlistId = item.url!!.toID()
val playlistId = item.url.toID()
val playlistName = item.name!!
PlaylistOptionsBottomSheet(playlistId, playlistName, PlaylistType.PUBLIC)
.show(

View File

@ -11,6 +11,8 @@ import com.github.libretube.obj.BackupFile
import com.github.libretube.obj.PreferenceItem
import com.github.libretube.util.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
class BackupDialog(
private val createBackupFile: (BackupFile) -> Unit
@ -45,8 +47,14 @@ class BackupDialog(
})
object Preferences : BackupOption(R.string.preferences, onSelected = { file ->
file.preferences = PreferenceHelper.settings.all.map {
PreferenceItem(it.key, it.value)
file.preferences = PreferenceHelper.settings.all.map { (key, value) ->
val jsonValue = when (value) {
is Number -> JsonPrimitive(value)
is Boolean -> JsonPrimitive(value)
is String -> JsonPrimitive(value)
else -> JsonNull
}
PreferenceItem(key, jsonValue)
}
})
}

View File

@ -8,6 +8,7 @@ import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.DeleteUserRequest
import com.github.libretube.databinding.DialogDeleteAccountBinding
import com.github.libretube.extensions.TAG
import com.github.libretube.util.PreferenceHelper
@ -41,10 +42,7 @@ class DeleteAccountDialog : DialogFragment() {
val token = PreferenceHelper.getToken()
try {
RetrofitInstance.authApi.deleteAccount(
token,
com.github.libretube.api.obj.DeleteUserRequest(password)
)
RetrofitInstance.authApi.deleteAccount(token, DeleteUserRequest(password))
} catch (e: Exception) {
Log.e(TAG(), e.toString())
Toast.makeText(context, R.string.unknown_error, Toast.LENGTH_SHORT).show()

View File

@ -74,7 +74,7 @@ class DownloadDialog(
}
private fun initDownloadOptions(streams: Streams) {
binding.fileName.setText(streams.title.toString())
binding.fileName.setText(streams.title)
val vidName = arrayListOf<String>()
@ -82,7 +82,7 @@ class DownloadDialog(
vidName.add(getString(R.string.no_video))
// add all available video streams
for (vid in streams.videoStreams!!) {
for (vid in streams.videoStreams) {
if (vid.url != null) {
val name = vid.quality + " " + vid.format
vidName.add(name)
@ -95,7 +95,7 @@ class DownloadDialog(
audioName.add(getString(R.string.no_audio))
// add all available audio streams
for (audio in streams.audioStreams!!) {
for (audio in streams.audioStreams) {
if (audio.url != null) {
val name = audio.quality + " " + audio.format
audioName.add(name)
@ -108,7 +108,7 @@ class DownloadDialog(
subtitleName.add(getString(R.string.no_subtitle))
// add all available subtitles
for (subtitle in streams.subtitles!!) {
for (subtitle in streams.subtitles) {
if (subtitle.url != null) {
subtitleName.add(subtitle.name.toString())
}

View File

@ -92,8 +92,8 @@ class LoginDialog : DialogFragment() {
Toast.LENGTH_SHORT
).show()
PreferenceHelper.setToken(response.token!!)
PreferenceHelper.setUsername(login.username!!)
PreferenceHelper.setToken(response.token)
PreferenceHelper.setUsername(login.username)
dialog?.dismiss()
activity?.recreate()

View File

@ -29,7 +29,7 @@ class UpdateDialog(
intent.putExtra("downloadUrl", downloadUrl)
context?.startService(intent)
} else {
val uri = Uri.parse(updateInfo.html_url)
val uri = Uri.parse(updateInfo.htmlUrl)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
startActivity(intent)
}
@ -40,8 +40,10 @@ class UpdateDialog(
private fun getDownloadUrl(updateInfo: UpdateInfo): String? {
val supportedArchitectures = Build.SUPPORTED_ABIS
supportedArchitectures.forEach { arch ->
updateInfo.assets?.forEach { asset ->
if (asset.name?.contains(arch) == true) return asset.browser_download_url
updateInfo.assets.forEach { asset ->
if (asset.name.contains(arch)) {
return asset.browserDownloadUrl
}
}
}
return null

View File

@ -142,7 +142,7 @@ class ChannelFragment : BaseFragment() {
binding.channelShare.setOnClickListener {
val shareDialog = ShareDialog(
response.id!!.toID(),
response.id.toID(),
ShareObjectType.CHANNEL,
shareData
)
@ -169,10 +169,10 @@ class ChannelFragment : BaseFragment() {
R.string.subscribers,
response.subscriberCount.formatShort()
)
if (response.description?.trim() == "") {
if (response.description.isBlank()) {
binding.channelDescription.visibility = View.GONE
} else {
binding.channelDescription.text = response.description?.trim()
binding.channelDescription.text = response.description.trim()
}
binding.channelDescription.setOnClickListener {
@ -186,13 +186,13 @@ class ChannelFragment : BaseFragment() {
// recyclerview of the videos by the channel
channelAdapter = VideosAdapter(
response.relatedStreams.orEmpty().toMutableList(),
response.relatedStreams.toMutableList(),
forceMode = VideosAdapter.Companion.ForceMode.CHANNEL
)
binding.channelRecView.adapter = channelAdapter
}
response.tabs?.let { setupTabs(it) }
setupTabs(response.tabs)
}
}
@ -230,16 +230,13 @@ class ChannelFragment : BaseFragment() {
private fun loadTab(tab: ChannelTab) {
scope.launch {
tab.data ?: return@launch
val response = try {
RetrofitInstance.api.getChannelTab(tab.data)
} catch (e: Exception) {
return@launch
}
val adapter = SearchAdapter(
response.content.toMutableList()
)
val adapter = SearchAdapter(response.content.toMutableList())
runOnUiThread {
binding.channelRecView.adapter = adapter
@ -275,7 +272,7 @@ class ChannelFragment : BaseFragment() {
return@launchWhenCreated
}
nextPage = response.nextpage
channelAdapter?.insertItems(response.relatedStreams.orEmpty())
channelAdapter?.insertItems(response.relatedStreams)
isLoading = false
binding.channelRefresh.isRefreshing = false
}
@ -291,9 +288,9 @@ class ChannelFragment : BaseFragment() {
) {
scope.launch {
val newContent = try {
RetrofitInstance.api.getChannelTab(tab.data ?: "", nextPage)
RetrofitInstance.api.getChannelTab(tab.data, nextPage)
} catch (e: Exception) {
e.printStackTrace()
Log.e(TAG(), "Exception: $e")
null
}
onNewNextPage.invoke(newContent?.nextpage)

View File

@ -37,9 +37,9 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.R
import com.github.libretube.api.CronetHelper
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.obj.ChapterSegment
import com.github.libretube.api.obj.PipedStream
@ -119,6 +119,8 @@ import kotlin.math.abs
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDate
import kotlinx.serialization.encodeToString
import org.chromium.net.CronetEngine
import retrofit2.HttpException
@ -685,9 +687,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
} else {
PlayingQueue.updateCurrent(streams.toStreamItem(videoId!!))
if (PlayerHelper.autoInsertRelatedVideos) {
PlayingQueue.add(
*streams.relatedStreams.orEmpty().toTypedArray()
)
PlayingQueue.add(*streams.relatedStreams.toTypedArray())
}
}
}
@ -748,7 +748,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
segmentData =
RetrofitInstance.api.getSegments(
videoId!!,
ObjectMapper().writeValueAsString(categories)
JsonHelper.json.encodeToString(categories)
)
if (segmentData.segments.isEmpty()) return@runCatching
playerBinding.exoProgress.setSegments(segmentData.segments)
@ -776,8 +776,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.liveDiff.text = "-$diffText"
}
// call the function again after 100ms
handler
.postDelayed(this@PlayerFragment::refreshLiveStatus, 100)
handler.postDelayed(this@PlayerFragment::refreshLiveStatus, 100)
}
// seek to saved watch position if available
@ -797,7 +796,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
return
}
// position is almost the end of the video => don't seek, start from beginning
if (position != null && position < streams.duration!! * 1000 * 0.9) {
if (position != null && position < streams.duration * 1000 * 0.9) {
exoPlayer.seekTo(position)
}
}
@ -826,7 +825,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.exoProgress.setPlayer(exoPlayer)
}
private fun localizedDate(date: String?): String? {
private fun localizedDate(date: LocalDate): String {
val locale = ConfigurationCompat.getLocales(resources.configuration)[0]!!
return TextUtils.localizeDate(date, locale)
}
@ -870,12 +869,12 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerChannelSubCount.text = context?.getString(
R.string.subscribers,
streams.uploaderSubscriberCount?.formatShort()
streams.uploaderSubscriberCount.formatShort()
)
}
// duration that's not greater than 0 indicates that the video is live
if (streams.duration!! <= 0) {
if (streams.duration <= 0) {
isLive = true
handleLiveVideo()
}
@ -883,10 +882,8 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.exoTitle.text = streams.title
// init the chapters recyclerview
if (streams.chapters != null) {
chapters = streams.chapters.orEmpty()
initializeChapters()
}
chapters = streams.chapters
initializeChapters()
// Listener for play and pause icon change
exoPlayer.addListener(object : Player.Listener {
@ -972,7 +969,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
})
binding.relPlayerDownload.setOnClickListener {
if (streams.duration!! <= 0) {
if (streams.duration <= 0) {
Toast.makeText(context, R.string.cannotDownload, Toast.LENGTH_SHORT).show()
} else if (!DownloadService.IS_DOWNLOAD_RUNNING) {
val newFragment = DownloadDialog(videoId!!)
@ -995,7 +992,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
}
initializeRelatedVideos(streams.relatedStreams)
// set video description
val description = streams.description!!
val description = streams.description
setupDescription(binding.playerDescription, description)
@ -1009,7 +1006,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
// update the subscribed state
binding.playerSubscribe.setupSubscriptionButton(
this.streams.uploaderUrl?.toID(),
this.streams.uploaderUrl.toID(),
this.streams.uploader
)
@ -1268,9 +1265,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
private fun setResolutionAndSubtitles() {
// create a list of subtitles
subtitles = mutableListOf()
val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!)
val subtitlesNamesList = mutableListOf(getString(R.string.none))
val subtitleCodesList = mutableListOf("")
streams.subtitles.orEmpty().forEach {
streams.subtitles.forEach {
subtitles.add(
SubtitleConfiguration.Builder(it.url!!.toUri())
.setMimeType(it.mimeType!!) // The correct MIME type (required).
@ -1300,7 +1297,7 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
if (defaultResolution != "") setPlayerResolution(defaultResolution.toInt())
if (!PreferenceHelper.getBoolean(PreferenceKeys.USE_HLS_OVER_DASH, false) &&
streams.videoStreams.orEmpty().isNotEmpty()
streams.videoStreams.isNotEmpty()
) {
val uri = let {
streams.dash?.toUri()
@ -1377,16 +1374,14 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
}
override fun onCaptionsClicked() {
if (!this@PlayerFragment::streams.isInitialized ||
streams.subtitles.isNullOrEmpty()
) {
if (!this@PlayerFragment::streams.isInitialized || streams.subtitles.isEmpty()) {
Toast.makeText(context, R.string.no_subtitles_available, Toast.LENGTH_SHORT).show()
return
}
val subtitlesNamesList = mutableListOf(context?.getString(R.string.none)!!)
val subtitlesNamesList = mutableListOf(getString(R.string.none))
val subtitleCodesList = mutableListOf("")
streams.subtitles!!.forEach {
streams.subtitles.forEach {
subtitlesNamesList += it.name!!
subtitleCodesList += it.code!!
}
@ -1515,9 +1510,9 @@ class PlayerFragment : BaseFragment(), OnlinePlayerOptions {
playerBinding.seekbarPreview.visibility = View.GONE
playerBinding.exoProgress.addListener(
SeekbarPreviewListener(
streams.previewFrames.orEmpty(),
streams.previewFrames,
playerBinding.seekbarPreview,
streams.duration!! * 1000
streams.duration * 1000
)
)
}

View File

@ -109,7 +109,7 @@ class PlaylistFragment : BaseFragment() {
Log.e(TAG(), e.toString())
return@launchWhenCreated
}
playlistFeed = response.relatedStreams.orEmpty().toMutableList()
playlistFeed = response.relatedStreams.toMutableList()
binding.playlistScrollview.visibility = View.VISIBLE
nextPage = response.nextpage
playlistName = response.name
@ -140,7 +140,7 @@ class PlaylistFragment : BaseFragment() {
if (playlistFeed.isEmpty()) return@setOnClickListener
NavigationHelper.navigateVideo(
requireContext(),
response.relatedStreams!!.first().url?.toID(),
response.relatedStreams.first().url?.toID(),
playlistId
)
}
@ -279,15 +279,9 @@ class PlaylistFragment : BaseFragment() {
val response = try {
// load locally stored playlists with the auth api
if (playlistType == PlaylistType.PRIVATE) {
RetrofitInstance.authApi.getPlaylistNextPage(
playlistId!!,
nextPage!!
)
RetrofitInstance.authApi.getPlaylistNextPage(playlistId!!, nextPage!!)
} else {
RetrofitInstance.api.getPlaylistNextPage(
playlistId!!,
nextPage!!
)
RetrofitInstance.api.getPlaylistNextPage(playlistId!!, nextPage!!)
}
} catch (e: Exception) {
Log.e(TAG(), e.toString())
@ -295,7 +289,7 @@ class PlaylistFragment : BaseFragment() {
}
nextPage = response.nextpage
playlistAdapter?.updateItems(response.relatedStreams!!)
playlistAdapter?.updateItems(response.relatedStreams)
isLoading = false
}
}

View File

@ -5,6 +5,7 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.R
@ -94,13 +95,9 @@ class SearchResultFragment : BaseFragment() {
return@launchWhenCreated
}
runOnUiThread {
searchAdapter = SearchAdapter(response.items.orEmpty().toMutableList())
searchAdapter = SearchAdapter(response.items.toMutableList())
binding.searchRecycler.adapter = searchAdapter
binding.noSearchResult.visibility = if (response.items.orEmpty().isEmpty()) {
View.VISIBLE
} else {
View.GONE
}
binding.noSearchResult.isVisible = response.items.isEmpty()
}
nextPage = response.nextpage
}
@ -124,8 +121,8 @@ class SearchResultFragment : BaseFragment() {
}
nextPage = response.nextpage!!
kotlin.runCatching {
if (response.items?.isNotEmpty() == true) {
searchAdapter.updateItems(response.items.toMutableList())
if (response.items.isNotEmpty()) {
searchAdapter.updateItems(response.items)
}
}
}

View File

@ -48,7 +48,7 @@ class CommentsViewModel : ViewModel() {
return@launch
}
val updatedPage = commentsPage.value?.apply {
comments = comments.plus(response.comments).toMutableList()
comments += response.comments
}
nextPage = response.nextpage
commentsPage.postValue(updatedPage)

View File

@ -68,7 +68,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
createBackupFile = registerForActivityResult(
CreateDocument("application/json")
) { uri: Uri? ->
BackupHelper(requireContext()).advancedBackup(uri, backupFile)
BackupHelper(requireContext()).createAdvancedBackup(uri, backupFile)
}
super.onCreate(savedInstanceState)

View File

@ -137,8 +137,8 @@ class InstanceSettings : BasePreferenceFragment() {
response?.sortBy { it.name }
instanceNames.addAll(response.orEmpty().map { it.name ?: "" })
instanceValues.addAll(response.orEmpty().map { it.api_url ?: "" })
instanceNames.addAll(response.orEmpty().map { it.name })
instanceValues.addAll(response.orEmpty().map { it.apiUrl })
customInstances.forEach { instance ->
instanceNames += instance.name

View File

@ -20,7 +20,7 @@ import kotlinx.coroutines.runBlocking
*/
class ChannelOptionsBottomSheet(
private val channelId: String,
private val channelName: String?
channelName: String?
) : BaseBottomSheet() {
private val shareData = ShareData(currentChannel = channelName)
override fun onCreate(savedInstanceState: Bundle?) {
@ -44,7 +44,7 @@ class ChannelOptionsBottomSheet(
val channel = runBlocking {
RetrofitInstance.api.getChannel(channelId)
}
channel.relatedStreams?.firstOrNull()?.url?.toID()?.let {
channel.relatedStreams.firstOrNull()?.url?.toID()?.let {
NavigationHelper.navigateVideo(
requireContext(),
it,
@ -60,7 +60,7 @@ class ChannelOptionsBottomSheet(
val channel = runBlocking {
RetrofitInstance.api.getChannel(channelId)
}
channel.relatedStreams?.firstOrNull()?.url?.toID()?.let {
channel.relatedStreams.firstOrNull()?.url?.toID()?.let {
BackgroundHelper.playOnBackground(
requireContext(),
videoId = it,

View File

@ -2,15 +2,22 @@ package com.github.libretube.util
import android.content.Context
import android.net.Uri
import android.util.Log
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.api.JsonHelper
import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.db.DatabaseHolder.Companion.Database
import com.github.libretube.extensions.TAG
import com.github.libretube.extensions.query
import com.github.libretube.obj.BackupFile
import com.github.libretube.obj.PreferenceItem
import java.io.FileOutputStream
import kotlinx.serialization.json.booleanOrNull
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import kotlinx.serialization.json.floatOrNull
import kotlinx.serialization.json.intOrNull
import kotlinx.serialization.json.longOrNull
/**
* Backup and restore the preferences
@ -19,18 +26,15 @@ class BackupHelper(private val context: Context) {
/**
* Write a [BackupFile] containing the database content as well as the preferences
*/
fun advancedBackup(uri: Uri?, backupFile: BackupFile) {
if (uri == null) return
try {
context.contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
fileOutputStream.write(
ObjectMapper().writeValueAsBytes(backupFile)
)
fun createAdvancedBackup(uri: Uri?, backupFile: BackupFile) {
uri?.let {
try {
context.contentResolver.openOutputStream(it)?.use { outputStream ->
JsonHelper.json.encodeToStream(backupFile, outputStream)
}
} catch (e: Exception) {
Log.e(TAG(), "Error while writing backup: $e")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
@ -38,36 +42,33 @@ class BackupHelper(private val context: Context) {
* Restore data from a [BackupFile]
*/
fun restoreAdvancedBackup(uri: Uri?) {
if (uri == null) return
val mapper = ObjectMapper()
val json = context.contentResolver.openInputStream(uri)?.use {
it.bufferedReader().use { reader -> reader.readText() }
}.orEmpty()
val backupFile = mapper.readValue(json, BackupFile::class.java)
val backupFile = uri?.let {
context.contentResolver.openInputStream(it)?.use { inputStream ->
JsonHelper.json.decodeFromStream<BackupFile>(inputStream)
}
} ?: return
query {
Database.watchHistoryDao().insertAll(
*backupFile.watchHistory.orEmpty().toTypedArray()
*backupFile.watchHistory.toTypedArray()
)
Database.searchHistoryDao().insertAll(
*backupFile.searchHistory.orEmpty().toTypedArray()
*backupFile.searchHistory.toTypedArray()
)
Database.watchPositionDao().insertAll(
*backupFile.watchPositions.orEmpty().toTypedArray()
*backupFile.watchPositions.toTypedArray()
)
Database.localSubscriptionDao().insertAll(
*backupFile.localSubscriptions.orEmpty().toTypedArray()
*backupFile.localSubscriptions.toTypedArray()
)
Database.customInstanceDao().insertAll(
*backupFile.customInstances.orEmpty().toTypedArray()
*backupFile.customInstances.toTypedArray()
)
Database.playlistBookmarkDao().insertAll(
*backupFile.playlistBookmarks.orEmpty().toTypedArray()
*backupFile.playlistBookmarks.toTypedArray()
)
backupFile.localPlaylists?.forEach {
backupFile.localPlaylists.forEach {
Database.localPlaylistsDao().createPlaylist(it.playlist)
val playlistId = Database.localPlaylistsDao().getAll().last().playlist.id
it.videos.forEach {
@ -90,18 +91,26 @@ class BackupHelper(private val context: Context) {
clear()
// decide for each preference which type it is and save it to the preferences
preferences.forEach {
when (it.value) {
is Boolean -> putBoolean(it.key, it.value)
is Float -> putFloat(it.key, it.value)
is Long -> putLong(it.key, it.value)
preferences.forEach { (key, jsonValue) ->
val value = if (jsonValue.isString) {
jsonValue.content
} else {
jsonValue.booleanOrNull
?: jsonValue.intOrNull
?: jsonValue.longOrNull
?: jsonValue.floatOrNull
}
when (value) {
is Boolean -> putBoolean(key, value)
is Float -> putFloat(key, value)
is Long -> putLong(key, value)
is Int -> {
when (it.key) {
PreferenceKeys.START_FRAGMENT -> putInt(it.key, it.value)
else -> putLong(it.key, it.value.toLong())
when (key) {
PreferenceKeys.START_FRAGMENT -> putInt(key, value)
else -> putLong(key, value.toLong())
}
}
is String -> putString(it.key, it.value)
is String -> putString(key, value)
}
}
}

View File

@ -4,8 +4,8 @@ import android.app.Activity
import android.net.Uri
import android.util.Log
import android.widget.Toast
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.R
import com.github.libretube.api.JsonHelper
import com.github.libretube.api.PlaylistsHelper
import com.github.libretube.api.RetrofitInstance
import com.github.libretube.api.SubscriptionHelper
@ -15,11 +15,13 @@ 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 java.io.FileOutputStream
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import okio.use
class ImportHelper(
private val activity: Activity
@ -56,12 +58,11 @@ class ImportHelper(
return when (val fileType = activity.contentResolver.getType(uri)) {
"application/json", "application/*", "application/octet-stream" -> {
// NewPipe subscriptions format
val subscriptions = ObjectMapper().readValue(
uri.readText(),
NewPipeSubscriptions::class.java
)
subscriptions.subscriptions.orEmpty().map {
it.url!!.replace("https://www.youtube.com/channel/", "")
val subscriptions = activity.contentResolver.openInputStream(uri)?.use {
JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it)
}
subscriptions?.subscriptions.orEmpty().map {
it.url.replace("https://www.youtube.com/channel/", "")
}
}
"text/csv", "text/comma-separated-values" -> {
@ -91,20 +92,14 @@ class ImportHelper(
SubscriptionHelper.getFormattedLocalSubscriptions()
)
}
val newPipeChannels = mutableListOf<NewPipeSubscription>()
subs.forEach {
newPipeChannels += NewPipeSubscription(
name = it.name,
service_id = 0,
url = "https://www.youtube.com" + it.url
)
val newPipeChannels = subs.map {
NewPipeSubscription(it.name, 0, "https://www.youtube.com${it.url}")
}
val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels)
val newPipeSubscriptions = NewPipeSubscriptions(
subscriptions = newPipeChannels
)
uri.write(newPipeSubscriptions)
activity.contentResolver.openOutputStream(uri)?.use {
JsonHelper.json.encodeToStream(newPipeSubscriptions, it)
}
activity.toastFromMainThread(R.string.exportsuccess)
}
@ -134,11 +129,10 @@ class ImportHelper(
}
}
"application/json", "application/*", "application/octet-stream" -> {
val playlistFile = ObjectMapper().readValue(
uri.readText(),
ImportPlaylistFile::class.java
)
importPlaylists.addAll(playlistFile.playlists.orEmpty())
val playlistFile = activity.contentResolver.openInputStream(uri)?.use {
JsonHelper.json.decodeFromStream<ImportPlaylistFile>(it)
}
importPlaylists.addAll(playlistFile?.playlists.orEmpty())
}
else -> {
activity.applicationContext.toastFromMainThread("Unsupported file type $fileType")
@ -167,31 +161,13 @@ class ImportHelper(
runBlocking {
val playlists = PlaylistsHelper.exportPlaylists()
val playlistFile = ImportPlaylistFile(
format = "Piped",
version = 1,
playlists = playlists
)
val playlistFile = ImportPlaylistFile("Piped", 1, playlists)
uri.write(playlistFile)
activity.contentResolver.openOutputStream(uri)?.use {
JsonHelper.json.encodeToStream(playlistFile, it)
}
activity.toastFromMainThread(R.string.exportsuccess)
}
}
private fun Uri.readText(): String {
return activity.contentResolver.openInputStream(this)?.use {
it.bufferedReader().use { reader -> reader.readText() }
}.orEmpty()
}
private fun Uri.write(text: Any) {
activity.contentResolver.openFileDescriptor(this, "w")?.use {
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
fileOutputStream.write(
ObjectMapper().writeValueAsBytes(text)
)
}
}
}
}

View File

@ -1,48 +0,0 @@
package com.github.libretube.util
import android.content.Context
import android.net.Uri
import com.fasterxml.jackson.databind.ObjectMapper
import com.github.libretube.api.obj.Streams
import java.io.File
import java.io.FileOutputStream
class MetadataHelper(
private val context: Context
) {
private val mapper = ObjectMapper()
private val metadataDir = DownloadHelper.getDownloadDir(context, DownloadHelper.METADATA_DIR)
fun createMetadata(fileName: String, streams: Streams) {
val targetFile = File(metadataDir, fileName)
targetFile.createNewFile()
context.contentResolver.openFileDescriptor(
Uri.fromFile(targetFile),
"w"
)?.use {
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
fileOutputStream.write(
mapper.writeValueAsBytes(
streams
)
)
}
}
}
fun getMetadata(fileName: String): Streams? {
val sourceFile = File(metadataDir, fileName)
return try {
val json = context.contentResolver.openInputStream(
Uri.fromFile(sourceFile)
)?.use {
it.bufferedReader().use { reader -> reader.readText() }
}
mapper.readValue(json, Streams::class.java)
} catch (e: Exception) {
return null
}
}
}

View File

@ -147,7 +147,7 @@ object PlayingQueue {
scope.launch {
while (channelNextPage != null) {
RetrofitInstance.api.getChannelNextPage(channelId, nextPage!!).apply {
add(*relatedStreams.orEmpty().toTypedArray())
add(*relatedStreams.toTypedArray())
channelNextPage = this.nextpage
}
}
@ -158,7 +158,7 @@ object PlayingQueue {
scope.launch {
runCatching {
val channel = RetrofitInstance.api.getChannel(channelId)
add(*channel.relatedStreams.orEmpty().toTypedArray())
add(*channel.relatedStreams.toTypedArray())
updateCurrent(newCurrentStream)
if (channel.nextpage == null) return@launch
fetchMoreFromChannel(channelId, channel.nextpage)

View File

@ -2,10 +2,11 @@ package com.github.libretube.util
import android.net.Uri
import java.net.URL
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.time.format.FormatStyle
import java.util.*
import kotlinx.datetime.LocalDate
import kotlinx.datetime.toJavaLocalDate
object TextUtils {
/**
@ -42,16 +43,9 @@ object TextUtils {
* @param locale The locale to use, otherwise uses system default
* return Localized date string
*/
fun localizeDate(date: String?, locale: Locale): String? {
date ?: return null
// relative time span
if (!date.contains("-")) return date
val dateObj = LocalDate.parse(date)
fun localizeDate(date: LocalDate, locale: Locale): String {
val formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale)
return dateObj.format(formatter)
return date.toJavaLocalDate().format(formatter)
}
/**

View File

@ -5,13 +5,15 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = '1.7.22'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files

View File

@ -11,13 +11,15 @@ espresso = "3.5.1"
workRuntime = "2.7.1"
exoplayer = "2.18.2"
retrofit = "2.9.0"
jacksonAnnotations = "2.13.4"
desugaring = "2.0.0"
cronetEmbedded = "108.5359.79"
cronetOkHttp = "0.1.0"
coil = "2.2.2"
leakcanary = "2.10"
room = "2.5.0"
kotlinxSerialization = "1.4.1"
kotlinxDatetime = "0.4.0"
kotlinxRetrofit = "0.8.0"
[libraries]
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
@ -33,8 +35,6 @@ androidx-work-runtime = { group = "androidx.work", name="work-runtime-ktx", vers
exoplayer = { group = "com.google.android.exoplayer", name = "exoplayer", version.ref = "exoplayer" }
exoplayer-extension-mediasession = { group = "com.google.android.exoplayer", name = "extension-mediasession", version.ref = "exoplayer" }
square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
square-retrofit-converterJackson = { group = "com.squareup.retrofit2", name = "converter-jackson", version.ref = "retrofit" }
jacksonAnnotations = { group = "com.fasterxml.jackson.core", name = "jackson-annotations", version.ref = "jacksonAnnotations" }
desugaring = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "desugaring" }
exoplayer-extension-cronet = { group = "com.google.android.exoplayer", name = "extension-cronet", version.ref = "exoplayer" }
exoplayer-dash = { group = "com.google.android.exoplayer", name = "exoplayer-dash", version.ref = "exoplayer" }
@ -46,4 +46,7 @@ lifecycle-viewmodel = { group = "androidx.lifecycle", name = "lifecycle-viewmode
lifecycle-runtime = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
lifecycle-livedata = { group = "androidx.lifecycle", name = "lifecycle-livedata-ktx", version.ref = "lifecycle" }
room = { group = "androidx.room", name="room-runtime", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
kotlinx-serialization = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-retrofit = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinxRetrofit" }