mirror of
https://github.com/libre-tube/LibreTube.git
synced 2024-12-12 21:30:30 +05:30
Merge pull request #6855 from Bnyro/master
feat: support for exporting single playlists
This commit is contained in:
commit
2b40807802
@ -14,10 +14,7 @@ import com.github.libretube.extensions.parallelMap
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.helpers.PreferenceHelper
|
||||
import com.github.libretube.helpers.ProxyHelper
|
||||
import com.github.libretube.obj.FreeTubeImportPlaylist
|
||||
import com.github.libretube.obj.FreeTubeVideo
|
||||
import com.github.libretube.obj.PipedImportPlaylist
|
||||
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL
|
||||
import com.github.libretube.util.deArrow
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
@ -201,35 +198,11 @@ object PlaylistsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportPipedPlaylists(): List<PipedImportPlaylist> = withContext(Dispatchers.IO) {
|
||||
getPlaylists()
|
||||
.map { async { getPlaylist(it.id!!) } }
|
||||
.awaitAll()
|
||||
.map {
|
||||
val videos = it.relatedStreams.map { item ->
|
||||
"$YOUTUBE_FRONTEND_URL/watch?v=${item.url!!.toID()}"
|
||||
}
|
||||
PipedImportPlaylist(it.name, "playlist", "private", videos)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun exportFreeTubePlaylists(): List<FreeTubeImportPlaylist> =
|
||||
suspend fun getAllPlaylistsWithVideos(playlistIds: List<String>? = null): List<Playlist> =
|
||||
withContext(Dispatchers.IO) {
|
||||
getPlaylists()
|
||||
.map { async { getPlaylist(it.id!!) } }
|
||||
(playlistIds ?: getPlaylists().map { it.id!! })
|
||||
.map { async { getPlaylist(it) } }
|
||||
.awaitAll()
|
||||
.map { playlist ->
|
||||
val videos = playlist.relatedStreams.map { videoInfo ->
|
||||
FreeTubeVideo(
|
||||
videoId = videoInfo.url.orEmpty().toID(),
|
||||
title = videoInfo.title.orEmpty(),
|
||||
author = videoInfo.uploaderName.orEmpty(),
|
||||
authorId = videoInfo.uploaderUrl.orEmpty().toID(),
|
||||
lengthSeconds = videoInfo.duration ?: 0L
|
||||
)
|
||||
}
|
||||
FreeTubeImportPlaylist(playlist.name.orEmpty(), videos)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun clonePlaylist(playlistId: String): String? {
|
||||
|
13
app/src/main/java/com/github/libretube/enums/ImportFormat.kt
Normal file
13
app/src/main/java/com/github/libretube/enums/ImportFormat.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package com.github.libretube.enums
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.github.libretube.R
|
||||
|
||||
enum class ImportFormat(@StringRes val value: Int, val fileExtension: String) {
|
||||
NEWPIPE(R.string.import_format_newpipe, "json"),
|
||||
FREETUBE(R.string.import_format_freetube, "json"),
|
||||
YOUTUBECSV(R.string.import_format_youtube_csv, "csv"),
|
||||
YOUTUBEJSON(R.string.youtube, "json"),
|
||||
PIPED(R.string.import_format_piped, "json"),
|
||||
URLSORIDS(R.string.import_format_list_of_urls, "txt")
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package com.github.libretube.enums
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.github.libretube.R
|
||||
|
||||
enum class ImportFormat(@StringRes val value: Int) {
|
||||
NEWPIPE(R.string.import_format_newpipe),
|
||||
FREETUBE(R.string.import_format_freetube),
|
||||
YOUTUBECSV(R.string.import_format_youtube_csv),
|
||||
YOUTUBEJSON(R.string.youtube),
|
||||
PIPED(R.string.import_format_piped),
|
||||
URLSORIDS(R.string.import_format_list_of_urls)
|
||||
}
|
@ -15,8 +15,10 @@ import com.github.libretube.db.DatabaseHolder.Database
|
||||
import com.github.libretube.db.obj.WatchHistoryItem
|
||||
import com.github.libretube.enums.ImportFormat
|
||||
import com.github.libretube.extensions.TAG
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||
import com.github.libretube.obj.FreeTubeImportPlaylist
|
||||
import com.github.libretube.obj.FreeTubeVideo
|
||||
import com.github.libretube.obj.FreetubeSubscription
|
||||
import com.github.libretube.obj.FreetubeSubscriptions
|
||||
import com.github.libretube.obj.NewPipeSubscription
|
||||
@ -24,7 +26,8 @@ import com.github.libretube.obj.NewPipeSubscriptions
|
||||
import com.github.libretube.obj.PipedImportPlaylist
|
||||
import com.github.libretube.obj.PipedPlaylistFile
|
||||
import com.github.libretube.obj.YouTubeWatchHistoryFileItem
|
||||
import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_FRONTEND_URL
|
||||
import com.github.libretube.ui.dialogs.ShareDialog.Companion.YOUTUBE_SHORT_URL
|
||||
import com.github.libretube.util.TextUtils
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.encodeToString
|
||||
@ -34,6 +37,7 @@ import java.util.stream.Collectors
|
||||
|
||||
object ImportHelper {
|
||||
private const val IMPORT_THUMBNAIL_QUALITY = "mqdefault"
|
||||
private const val VIDEO_ID_LENGTH = 11
|
||||
|
||||
/**
|
||||
* Import subscriptions by a file uri
|
||||
@ -70,7 +74,7 @@ object ImportHelper {
|
||||
JsonHelper.json.decodeFromStream<NewPipeSubscriptions>(it)
|
||||
}
|
||||
subscriptions?.subscriptions.orEmpty().map {
|
||||
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "")
|
||||
it.url.replace("$YOUTUBE_FRONTEND_URL/channel/", "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +83,7 @@ object ImportHelper {
|
||||
JsonHelper.json.decodeFromStream<FreetubeSubscriptions>(it)
|
||||
}
|
||||
subscriptions?.subscriptions.orEmpty().map {
|
||||
it.url.replace("${ShareDialog.YOUTUBE_FRONTEND_URL}/channel/", "")
|
||||
it.url.replace("$YOUTUBE_FRONTEND_URL/channel/", "")
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +118,7 @@ object ImportHelper {
|
||||
when (importFormat) {
|
||||
ImportFormat.NEWPIPE -> {
|
||||
val newPipeChannels = subs.map {
|
||||
NewPipeSubscription(it.name, 0, "${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}")
|
||||
NewPipeSubscription(it.name, 0, "$YOUTUBE_FRONTEND_URL${it.url}")
|
||||
}
|
||||
val newPipeSubscriptions = NewPipeSubscriptions(subscriptions = newPipeChannels)
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
@ -127,7 +131,7 @@ object ImportHelper {
|
||||
FreetubeSubscription(
|
||||
it.name,
|
||||
"",
|
||||
"${ShareDialog.YOUTUBE_FRONTEND_URL}${it.url}"
|
||||
"$YOUTUBE_FRONTEND_URL${it.url}"
|
||||
)
|
||||
}
|
||||
val freeTubeSubscriptions = FreetubeSubscriptions(subscriptions = freeTubeChannels)
|
||||
@ -158,7 +162,7 @@ object ImportHelper {
|
||||
|
||||
// convert the YouTube URLs to videoIds
|
||||
importPlaylists.forEach { playlist ->
|
||||
playlist.videos = playlist.videos.map { it.takeLast(11) }
|
||||
playlist.videos = playlist.videos.map { it.takeLast(VIDEO_ID_LENGTH) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -225,7 +229,7 @@ object ImportHelper {
|
||||
|
||||
// convert the YouTube URLs to videoIds
|
||||
importPlaylists.forEach { importPlaylist ->
|
||||
importPlaylist.videos = importPlaylist.videos.map { it.takeLast(11) }
|
||||
importPlaylist.videos = importPlaylist.videos.map { it.takeLast(VIDEO_ID_LENGTH) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -236,7 +240,7 @@ object ImportHelper {
|
||||
playlist.videos = inputStream.bufferedReader().readLines()
|
||||
.flatMap { it.split(",") }
|
||||
.mapNotNull { videoUrlOrId ->
|
||||
if (videoUrlOrId.length == 11) {
|
||||
if (videoUrlOrId.length == VIDEO_ID_LENGTH) {
|
||||
videoUrlOrId
|
||||
} else {
|
||||
TextUtils.getVideoIdFromUri(videoUrlOrId.toUri())
|
||||
@ -272,11 +276,22 @@ object ImportHelper {
|
||||
* Export Playlists
|
||||
*/
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
suspend fun exportPlaylists(activity: Activity, uri: Uri, importFormat: ImportFormat) {
|
||||
suspend fun exportPlaylists(
|
||||
activity: Activity,
|
||||
uri: Uri,
|
||||
importFormat: ImportFormat,
|
||||
selectedPlaylistIds: List<String>? = null
|
||||
) {
|
||||
val playlists = PlaylistsHelper.getAllPlaylistsWithVideos(selectedPlaylistIds)
|
||||
|
||||
when (importFormat) {
|
||||
ImportFormat.PIPED -> {
|
||||
val playlists = PlaylistsHelper.exportPipedPlaylists()
|
||||
val playlistFile = PipedPlaylistFile(playlists = playlists)
|
||||
val playlistFile = PipedPlaylistFile(playlists = playlists.map {
|
||||
val videos = it.relatedStreams.map { item ->
|
||||
"$YOUTUBE_FRONTEND_URL/watch?v=${item.url!!.toID()}"
|
||||
}
|
||||
PipedImportPlaylist(it.name, "playlist", "private", videos)
|
||||
})
|
||||
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
JsonHelper.json.encodeToStream(playlistFile, it)
|
||||
@ -285,17 +300,38 @@ object ImportHelper {
|
||||
}
|
||||
|
||||
ImportFormat.FREETUBE -> {
|
||||
val playlists = PlaylistsHelper.exportFreeTubePlaylists()
|
||||
|
||||
val freeTubeExportDb = playlists.joinToString("\n") { playlist ->
|
||||
val freeTubeExportDb = playlists.map { playlist ->
|
||||
val videos = playlist.relatedStreams.map { videoInfo ->
|
||||
FreeTubeVideo(
|
||||
videoId = videoInfo.url.orEmpty().toID(),
|
||||
title = videoInfo.title.orEmpty(),
|
||||
author = videoInfo.uploaderName.orEmpty(),
|
||||
authorId = videoInfo.uploaderUrl.orEmpty().toID(),
|
||||
lengthSeconds = videoInfo.duration ?: 0L
|
||||
)
|
||||
}
|
||||
FreeTubeImportPlaylist(playlist.name.orEmpty(), videos)
|
||||
}.joinToString("\n") { playlist ->
|
||||
JsonHelper.json.encodeToString(playlist)
|
||||
}
|
||||
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(freeTubeExportDb.toByteArray())
|
||||
}
|
||||
activity.toastFromMainDispatcher(R.string.exportsuccess)
|
||||
}
|
||||
|
||||
ImportFormat.URLSORIDS -> {
|
||||
val urlListExport = playlists
|
||||
.flatMap { it.relatedStreams }
|
||||
.joinToString("\n") { YOUTUBE_SHORT_URL + "/watch?v=" + it.url!!.toID() }
|
||||
|
||||
activity.contentResolver.openOutputStream(uri)?.use {
|
||||
it.write(urlListExport.toByteArray())
|
||||
}
|
||||
activity.toastFromMainDispatcher(R.string.exportsuccess)
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
@ -311,7 +347,7 @@ object ImportHelper {
|
||||
.filter { it.activityControls.contains("YouTube watch history") && it.subtitles.isNotEmpty() && it.titleUrl.isNotEmpty() }
|
||||
.reversed()
|
||||
.map {
|
||||
val videoId = it.titleUrl.substring(it.titleUrl.length - 11)
|
||||
val videoId = it.titleUrl.takeLast(VIDEO_ID_LENGTH)
|
||||
|
||||
WatchHistoryItem(
|
||||
videoId = videoId,
|
||||
|
@ -12,6 +12,7 @@ import android.view.View
|
||||
import android.view.ViewTreeObserver
|
||||
import android.widget.ScrollView
|
||||
import androidx.activity.addCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.widget.SearchView
|
||||
@ -36,8 +37,10 @@ import com.github.libretube.compat.PictureInPictureCompat
|
||||
import com.github.libretube.constants.IntentData
|
||||
import com.github.libretube.constants.PreferenceKeys
|
||||
import com.github.libretube.databinding.ActivityMainBinding
|
||||
import com.github.libretube.enums.ImportFormat
|
||||
import com.github.libretube.extensions.anyChildFocused
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.helpers.ImportHelper
|
||||
import com.github.libretube.helpers.IntentHelper
|
||||
import com.github.libretube.helpers.NavBarHelper
|
||||
import com.github.libretube.helpers.NavigationHelper
|
||||
@ -54,11 +57,14 @@ import com.github.libretube.ui.fragments.PlayerFragment
|
||||
import com.github.libretube.ui.models.CommonPlayerViewModel
|
||||
import com.github.libretube.ui.models.SearchViewModel
|
||||
import com.github.libretube.ui.models.SubscriptionsViewModel
|
||||
import com.github.libretube.ui.preferences.BackupRestoreSettings.Companion.FILETYPE_ANY
|
||||
import com.github.libretube.ui.preferences.BackupRestoreSettings.Companion.JSON
|
||||
import com.github.libretube.util.UpdateChecker
|
||||
import com.google.android.material.elevation.SurfaceColors
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import kotlin.math.exp
|
||||
|
||||
class MainActivity : BaseActivity() {
|
||||
lateinit var binding: ActivityMainBinding
|
||||
@ -75,6 +81,25 @@ class MainActivity : BaseActivity() {
|
||||
private var savedSearchQuery: String? = null
|
||||
private var shouldOpenSuggestions = true
|
||||
|
||||
// registering for activity results is only possible, this here should have been part of
|
||||
// PlaylistOptionsBottomSheet instead if Android allowed us to
|
||||
private var playlistExportFormat: ImportFormat = ImportFormat.NEWPIPE
|
||||
private var exportPlaylistId: String? = null
|
||||
private val createPlaylistsFile = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument(FILETYPE_ANY)
|
||||
) { uri ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
ImportHelper.exportPlaylists(
|
||||
this@MainActivity,
|
||||
uri,
|
||||
playlistExportFormat,
|
||||
selectedPlaylistIds = listOf(exportPlaylistId!!)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@ -652,4 +677,10 @@ class MainActivity : BaseActivity() {
|
||||
?.let(action)
|
||||
?: false
|
||||
}
|
||||
|
||||
fun startPlaylistExport(playlistId: String, playlistName: String, format: ImportFormat) {
|
||||
playlistExportFormat = format
|
||||
exportPlaylistId = playlistId
|
||||
createPlaylistsFile.launch("${playlistName}.${format.fileExtension}")
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.github.libretube.ui.preferences
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
@ -29,26 +30,6 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
private val backupDateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH:mm:ss")
|
||||
private var backupFile = BackupFile()
|
||||
private var importFormat: ImportFormat = ImportFormat.NEWPIPE
|
||||
private val importSubscriptionFormatList = listOf(
|
||||
ImportFormat.NEWPIPE,
|
||||
ImportFormat.FREETUBE,
|
||||
ImportFormat.YOUTUBECSV
|
||||
)
|
||||
private val exportSubscriptionFormatList = listOf(
|
||||
ImportFormat.NEWPIPE,
|
||||
ImportFormat.FREETUBE
|
||||
)
|
||||
private val importPlaylistFormatList = listOf(
|
||||
ImportFormat.PIPED,
|
||||
ImportFormat.FREETUBE,
|
||||
ImportFormat.YOUTUBECSV,
|
||||
ImportFormat.URLSORIDS
|
||||
)
|
||||
private val exportPlaylistFormatList = listOf(
|
||||
ImportFormat.PIPED,
|
||||
ImportFormat.FREETUBE
|
||||
)
|
||||
private val importWatchHistoryFormatList = listOf(ImportFormat.YOUTUBEJSON)
|
||||
|
||||
override val titleResourceId: Int = R.string.backup_restore
|
||||
|
||||
@ -66,7 +47,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
private val createBackupFile = registerForActivityResult(CreateDocument(JSON)) { uri ->
|
||||
private val createBackupFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
BackupHelper.createAdvancedBackup(requireContext(), uri, backupFile)
|
||||
@ -85,7 +66,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val createSubscriptionsFile = registerForActivityResult(CreateDocument(JSON)) { uri ->
|
||||
private val createSubscriptionsFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
|
||||
if (uri == null) return@registerForActivityResult
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
ImportHelper.exportSubscriptions(requireActivity(), uri, importFormat)
|
||||
@ -112,7 +93,7 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private val createPlaylistsFile = registerForActivityResult(CreateDocument(JSON)) { uri ->
|
||||
private val createPlaylistsFile = registerForActivityResult(CreateDocument(FILETYPE_ANY)) { uri ->
|
||||
uri?.let {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
ImportHelper.exportPlaylists(requireActivity(), uri, importFormat)
|
||||
@ -120,32 +101,13 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun createImportFormatDialog(
|
||||
@StringRes titleStringId: Int,
|
||||
items: List<String>,
|
||||
onConfirm: (Int) -> Unit
|
||||
) {
|
||||
var selectedIndex = 0
|
||||
MaterialAlertDialogBuilder(this.requireContext())
|
||||
.setTitle(getString(titleStringId))
|
||||
.setSingleChoiceItems(items.toTypedArray(), selectedIndex) { _, i ->
|
||||
selectedIndex = i
|
||||
}
|
||||
.setPositiveButton(
|
||||
R.string.okay
|
||||
) { _, _ -> onConfirm(selectedIndex) }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.import_export_settings, rootKey)
|
||||
|
||||
val importSubscriptions = findPreference<Preference>("import_subscriptions")
|
||||
importSubscriptions?.setOnPreferenceClickListener {
|
||||
val list = importSubscriptionFormatList.map { getString(it.value) }
|
||||
createImportFormatDialog(R.string.import_subscriptions_from, list) {
|
||||
importFormat = importSubscriptionFormatList[it]
|
||||
createImportFormatDialog(requireContext(), R.string.import_subscriptions_from, importSubscriptionFormatList) {
|
||||
importFormat = it
|
||||
getSubscriptionsFile.launch("*/*")
|
||||
}
|
||||
true
|
||||
@ -153,11 +115,10 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
|
||||
val exportSubscriptions = findPreference<Preference>("export_subscriptions")
|
||||
exportSubscriptions?.setOnPreferenceClickListener {
|
||||
val list = exportSubscriptionFormatList.map { getString(it.value) }
|
||||
createImportFormatDialog(R.string.export_subscriptions_to, list) {
|
||||
importFormat = exportSubscriptionFormatList[it]
|
||||
createImportFormatDialog(requireContext(), R.string.export_subscriptions_to, exportSubscriptionFormatList) {
|
||||
importFormat = it
|
||||
createSubscriptionsFile.launch(
|
||||
"${getString(importFormat.value).lowercase()}-subscriptions.json"
|
||||
"${getString(importFormat.value).lowercase()}-subscriptions.${importFormat.fileExtension}"
|
||||
)
|
||||
}
|
||||
true
|
||||
@ -165,9 +126,8 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
|
||||
val importPlaylists = findPreference<Preference>("import_playlists")
|
||||
importPlaylists?.setOnPreferenceClickListener {
|
||||
val list = importPlaylistFormatList.map { getString(it.value) }
|
||||
createImportFormatDialog(R.string.import_playlists_from, list) {
|
||||
importFormat = importPlaylistFormatList[it]
|
||||
createImportFormatDialog(requireContext(), R.string.import_playlists_from, importPlaylistFormatList) {
|
||||
importFormat = it
|
||||
getPlaylistsFile.launch(arrayOf("*/*"))
|
||||
}
|
||||
true
|
||||
@ -175,11 +135,10 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
|
||||
val exportPlaylists = findPreference<Preference>("export_playlists")
|
||||
exportPlaylists?.setOnPreferenceClickListener {
|
||||
val list = exportPlaylistFormatList.map { getString(it.value) }
|
||||
createImportFormatDialog(R.string.export_playlists_to, list) {
|
||||
importFormat = exportPlaylistFormatList[it]
|
||||
createImportFormatDialog(requireContext(), R.string.export_playlists_to, exportPlaylistFormatList) {
|
||||
importFormat = it
|
||||
createPlaylistsFile.launch(
|
||||
"${getString(importFormat.value).lowercase()}-playlists.json"
|
||||
"${getString(importFormat.value).lowercase()}-playlists.${importFormat.fileExtension}"
|
||||
)
|
||||
}
|
||||
true
|
||||
@ -187,9 +146,8 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
|
||||
val importWatchHistory = findPreference<Preference>("import_watch_history")
|
||||
importWatchHistory?.setOnPreferenceClickListener {
|
||||
val list = importWatchHistoryFormatList.map { getString(it.value) }
|
||||
createImportFormatDialog(R.string.import_watch_history, list) {
|
||||
importFormat = importWatchHistoryFormatList[it]
|
||||
createImportFormatDialog(requireContext(), R.string.import_watch_history, importWatchHistoryFormatList) {
|
||||
importFormat = it
|
||||
getWatchHistoryFile.launch(arrayOf("*/*"))
|
||||
}
|
||||
true
|
||||
@ -219,5 +177,50 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
||||
|
||||
companion object {
|
||||
const val JSON = "application/json"
|
||||
|
||||
/**
|
||||
* Mimetype to use to create new files when setting extension manually
|
||||
*/
|
||||
const val FILETYPE_ANY = "application/octet-stream"
|
||||
|
||||
val importSubscriptionFormatList = listOf(
|
||||
ImportFormat.NEWPIPE,
|
||||
ImportFormat.FREETUBE,
|
||||
ImportFormat.YOUTUBECSV
|
||||
)
|
||||
val exportSubscriptionFormatList = listOf(
|
||||
ImportFormat.NEWPIPE,
|
||||
ImportFormat.FREETUBE
|
||||
)
|
||||
val importPlaylistFormatList = listOf(
|
||||
ImportFormat.PIPED,
|
||||
ImportFormat.FREETUBE,
|
||||
ImportFormat.YOUTUBECSV,
|
||||
ImportFormat.URLSORIDS
|
||||
)
|
||||
val exportPlaylistFormatList = listOf(
|
||||
ImportFormat.PIPED,
|
||||
ImportFormat.FREETUBE
|
||||
)
|
||||
val importWatchHistoryFormatList = listOf(ImportFormat.YOUTUBEJSON)
|
||||
|
||||
fun createImportFormatDialog(
|
||||
context: Context,
|
||||
@StringRes titleStringId: Int,
|
||||
formats: List<ImportFormat>,
|
||||
onConfirm: (ImportFormat) -> Unit
|
||||
) {
|
||||
var selectedIndex = 0
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(context.getString(titleStringId))
|
||||
.setSingleChoiceItems(formats.map { context.getString(it.value) }.toTypedArray(), selectedIndex) { _, i ->
|
||||
selectedIndex = i
|
||||
}
|
||||
.setPositiveButton(
|
||||
R.string.okay
|
||||
) { _, _ -> onConfirm(formats[selectedIndex]) }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,19 +7,23 @@ import com.github.libretube.api.PlaylistsHelper
|
||||
import com.github.libretube.api.RetrofitInstance
|
||||
import com.github.libretube.constants.IntentData
|
||||
import com.github.libretube.db.DatabaseHolder
|
||||
import com.github.libretube.enums.ImportFormat
|
||||
import com.github.libretube.enums.PlaylistType
|
||||
import com.github.libretube.enums.ShareObjectType
|
||||
import com.github.libretube.extensions.serializable
|
||||
import com.github.libretube.extensions.toID
|
||||
import com.github.libretube.extensions.toastFromMainDispatcher
|
||||
import com.github.libretube.helpers.BackgroundHelper
|
||||
import com.github.libretube.helpers.ContextHelper
|
||||
import com.github.libretube.helpers.DownloadHelper
|
||||
import com.github.libretube.obj.ShareData
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import com.github.libretube.ui.base.BaseActivity
|
||||
import com.github.libretube.ui.dialogs.DeletePlaylistDialog
|
||||
import com.github.libretube.ui.dialogs.PlaylistDescriptionDialog
|
||||
import com.github.libretube.ui.dialogs.RenamePlaylistDialog
|
||||
import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import com.github.libretube.ui.preferences.BackupRestoreSettings
|
||||
import com.github.libretube.util.PlayingQueue
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runBlocking
|
||||
@ -30,7 +34,11 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() {
|
||||
private lateinit var playlistId: String
|
||||
private lateinit var playlistType: PlaylistType
|
||||
|
||||
private var exportFormat: ImportFormat = ImportFormat.NEWPIPE
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
arguments?.let {
|
||||
playlistName = it.getString(IntentData.playlistName)!!
|
||||
playlistId = it.getString(IntentData.playlistId)!!
|
||||
@ -57,6 +65,7 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() {
|
||||
if (isBookmarked) R.string.remove_bookmark else R.string.add_to_bookmarks
|
||||
)
|
||||
} else {
|
||||
optionsList.add(R.string.export_playlist)
|
||||
optionsList.add(R.string.renamePlaylist)
|
||||
optionsList.add(R.string.change_playlist_description)
|
||||
optionsList.add(R.string.deletePlaylist)
|
||||
@ -139,7 +148,27 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() {
|
||||
}
|
||||
|
||||
R.string.download -> {
|
||||
DownloadHelper.startDownloadPlaylistDialog(requireContext(), mFragmentManager, playlistId, playlistName, playlistType)
|
||||
DownloadHelper.startDownloadPlaylistDialog(
|
||||
requireContext(),
|
||||
mFragmentManager,
|
||||
playlistId,
|
||||
playlistName,
|
||||
playlistType
|
||||
)
|
||||
}
|
||||
|
||||
R.string.export_playlist -> {
|
||||
val context = requireContext()
|
||||
|
||||
BackupRestoreSettings.createImportFormatDialog(
|
||||
context,
|
||||
R.string.export_playlist,
|
||||
BackupRestoreSettings.exportPlaylistFormatList + listOf(ImportFormat.URLSORIDS)
|
||||
) {
|
||||
exportFormat = it
|
||||
ContextHelper.unwrapActivity<MainActivity>(context)
|
||||
.startPlaylistExport(playlistId, playlistName, exportFormat)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
@ -158,7 +187,6 @@ class PlaylistOptionsBottomSheet : BaseBottomSheet() {
|
||||
}
|
||||
}
|
||||
}
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -362,6 +362,7 @@
|
||||
<string name="all_caught_up_summary">You\'ve seen all new videos</string>
|
||||
<string name="import_playlists">Import playlists</string>
|
||||
<string name="export_playlists">Export playlists</string>
|
||||
<string name="export_playlist">Export playlist</string>
|
||||
<string name="import_watch_history">Import watch history</string>
|
||||
<string name="import_watch_history_desc">Please note that not everything will be imported due to YouTube\'s limited export data.</string>
|
||||
<string name="app_backup">App Backup</string>
|
||||
@ -480,7 +481,7 @@
|
||||
<string name="import_format_freetube" translatable="false">FreeTube</string>
|
||||
<string name="import_format_youtube_csv" translatable="false">YouTube (CSV)</string>
|
||||
<string name="import_format_youtube_json" translatable="false">YouTube (JSON)</string>
|
||||
<string name="import_format_list_of_urls">List of URls or video IDs</string>
|
||||
<string name="import_format_list_of_urls">List of URLs or video IDs</string>
|
||||
<string name="home_tab_content">Home tab content</string>
|
||||
<string name="show_search_suggestions">Show search suggestions</string>
|
||||
<string name="audio_track_format">%1$s - %2$s</string>
|
||||
|
Loading…
Reference in New Issue
Block a user