mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
Merge pull request #2160 from Bnyro/playlists-import-export
Playlists import export
This commit is contained in:
commit
db739b9000
@ -6,16 +6,21 @@ import com.github.libretube.R
|
|||||||
import com.github.libretube.api.obj.Playlist
|
import com.github.libretube.api.obj.Playlist
|
||||||
import com.github.libretube.api.obj.PlaylistId
|
import com.github.libretube.api.obj.PlaylistId
|
||||||
import com.github.libretube.api.obj.Playlists
|
import com.github.libretube.api.obj.Playlists
|
||||||
|
import com.github.libretube.constants.YOUTUBE_FRONTEND_URL
|
||||||
import com.github.libretube.db.DatabaseHolder
|
import com.github.libretube.db.DatabaseHolder
|
||||||
import com.github.libretube.db.obj.LocalPlaylist
|
import com.github.libretube.db.obj.LocalPlaylist
|
||||||
import com.github.libretube.enums.PlaylistType
|
import com.github.libretube.enums.PlaylistType
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.extensions.awaitQuery
|
import com.github.libretube.extensions.awaitQuery
|
||||||
|
import com.github.libretube.extensions.toID
|
||||||
import com.github.libretube.extensions.toLocalPlaylistItem
|
import com.github.libretube.extensions.toLocalPlaylistItem
|
||||||
import com.github.libretube.extensions.toStreamItem
|
import com.github.libretube.extensions.toStreamItem
|
||||||
import com.github.libretube.extensions.toastFromMainThread
|
import com.github.libretube.extensions.toastFromMainThread
|
||||||
|
import com.github.libretube.obj.ImportPlaylist
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import com.github.libretube.util.ProxyHelper
|
import com.github.libretube.util.ProxyHelper
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@ -46,9 +51,9 @@ object PlaylistsHelper {
|
|||||||
return playlists
|
return playlists
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getPlaylist(playlistType: PlaylistType, playlistId: String): Playlist {
|
suspend fun getPlaylist(playlistId: String): Playlist {
|
||||||
// load locally stored playlists with the auth api
|
// load locally stored playlists with the auth api
|
||||||
return when (playlistType) {
|
return when (getPlaylistType()) {
|
||||||
PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId)
|
PlaylistType.PRIVATE -> RetrofitInstance.authApi.getPlaylist(playlistId)
|
||||||
PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId)
|
PlaylistType.PUBLIC -> RetrofitInstance.api.getPlaylist(playlistId)
|
||||||
PlaylistType.LOCAL -> {
|
PlaylistType.LOCAL -> {
|
||||||
@ -65,7 +70,10 @@ object PlaylistsHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createPlaylist(playlistName: String, appContext: Context, onSuccess: () -> Unit) {
|
suspend fun createPlaylist(
|
||||||
|
playlistName: String,
|
||||||
|
appContext: Context
|
||||||
|
): String? {
|
||||||
if (!loggedIn()) {
|
if (!loggedIn()) {
|
||||||
awaitQuery {
|
awaitQuery {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().createPlaylist(
|
DatabaseHolder.Database.localPlaylistsDao().createPlaylist(
|
||||||
@ -75,8 +83,9 @@ object PlaylistsHelper {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onSuccess.invoke()
|
return awaitQuery {
|
||||||
return
|
DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
|
}.last().playlist.id.toString()
|
||||||
}
|
}
|
||||||
val response = try {
|
val response = try {
|
||||||
RetrofitInstance.authApi.createPlaylist(
|
RetrofitInstance.authApi.createPlaylist(
|
||||||
@ -85,37 +94,39 @@ object PlaylistsHelper {
|
|||||||
)
|
)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
appContext.toastFromMainThread(R.string.unknown_error)
|
appContext.toastFromMainThread(R.string.unknown_error)
|
||||||
return
|
return null
|
||||||
} catch (e: HttpException) {
|
} catch (e: HttpException) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
appContext.toastFromMainThread(R.string.server_error)
|
appContext.toastFromMainThread(R.string.server_error)
|
||||||
return
|
return null
|
||||||
}
|
}
|
||||||
if (response.playlistId != null) {
|
if (response.playlistId != null) {
|
||||||
appContext.toastFromMainThread(R.string.playlistCreated)
|
appContext.toastFromMainThread(R.string.playlistCreated)
|
||||||
onSuccess.invoke()
|
return response.playlistId!!
|
||||||
} else {
|
|
||||||
appContext.toastFromMainThread(R.string.unknown_error)
|
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addToPlaylist(playlistId: String, videoId: String): Boolean {
|
suspend fun addToPlaylist(playlistId: String, vararg videoIds: String): Boolean {
|
||||||
if (!loggedIn()) {
|
if (!loggedIn()) {
|
||||||
val localPlaylistItem = RetrofitInstance.api.getStreams(videoId).toLocalPlaylistItem(playlistId, videoId)
|
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
||||||
awaitQuery {
|
.first { it.playlist.id.toString() == playlistId }
|
||||||
// avoid duplicated videos in a playlist
|
|
||||||
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByVideoId(playlistId, videoId)
|
|
||||||
|
|
||||||
// add the new video to the database
|
for (videoId in videoIds) {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
val localPlaylistItem = RetrofitInstance.api.getStreams(videoId).toLocalPlaylistItem(playlistId, videoId)
|
||||||
val localPlaylist = DatabaseHolder.Database.localPlaylistsDao().getAll()
|
awaitQuery {
|
||||||
.first { it.playlist.id.toString() == playlistId }
|
// avoid duplicated videos in a playlist
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().deletePlaylistItemsByVideoId(playlistId, videoId)
|
||||||
|
|
||||||
if (localPlaylist.playlist.thumbnailUrl == "") {
|
// add the new video to the database
|
||||||
// set the new playlist thumbnail URL
|
DatabaseHolder.Database.localPlaylistsDao().addPlaylistVideo(localPlaylistItem)
|
||||||
localPlaylistItem.thumbnailUrl?.let {
|
|
||||||
localPlaylist.playlist.thumbnailUrl = it
|
if (localPlaylist.playlist.thumbnailUrl == "") {
|
||||||
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(localPlaylist.playlist)
|
// set the new playlist thumbnail URL
|
||||||
|
localPlaylistItem.thumbnailUrl?.let {
|
||||||
|
localPlaylist.playlist.thumbnailUrl = it
|
||||||
|
DatabaseHolder.Database.localPlaylistsDao().updatePlaylist(localPlaylist.playlist)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,7 +135,10 @@ object PlaylistsHelper {
|
|||||||
|
|
||||||
return RetrofitInstance.authApi.addToPlaylist(
|
return RetrofitInstance.authApi.addToPlaylist(
|
||||||
token,
|
token,
|
||||||
PlaylistId(playlistId, videoId)
|
PlaylistId(
|
||||||
|
playlistId = playlistId,
|
||||||
|
videoIds = videoIds.toList()
|
||||||
|
)
|
||||||
).message == "ok"
|
).message == "ok"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,11 +189,49 @@ object PlaylistsHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPrivateType(): PlaylistType {
|
suspend fun importPlaylists(appContext: Context, playlists: List<ImportPlaylist>) {
|
||||||
|
for (playlist in playlists) {
|
||||||
|
val playlistId = createPlaylist(playlist.name!!, appContext) ?: continue
|
||||||
|
addToPlaylist(
|
||||||
|
playlistId,
|
||||||
|
*playlist.videos.map {
|
||||||
|
it.substringAfter("=")
|
||||||
|
}.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun exportPlaylists(): List<ImportPlaylist> {
|
||||||
|
val playlists = getPlaylists()
|
||||||
|
val importLists = mutableListOf<ImportPlaylist>()
|
||||||
|
runBlocking {
|
||||||
|
val tasks = playlists.map {
|
||||||
|
async {
|
||||||
|
val list = getPlaylist(it.id!!)
|
||||||
|
importLists.add(
|
||||||
|
ImportPlaylist(
|
||||||
|
name = list.name,
|
||||||
|
type = "playlist",
|
||||||
|
visibility = "private",
|
||||||
|
videos = list.relatedStreams.orEmpty().map {
|
||||||
|
YOUTUBE_FRONTEND_URL + "/watch?v=" + it.url!!.toID()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tasks.forEach {
|
||||||
|
it.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return importLists
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPlaylistType(): PlaylistType {
|
||||||
return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL
|
return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPrivateType(playlistId: String): PlaylistType {
|
fun getPlaylistType(playlistId: String): PlaylistType {
|
||||||
if (playlistId.all { it.isDigit() }) return PlaylistType.LOCAL
|
if (playlistId.all { it.isDigit() }) return PlaylistType.LOCAL
|
||||||
if (playlistId.matches(pipedPlaylistRegex)) return PlaylistType.PRIVATE
|
if (playlistId.matches(pipedPlaylistRegex)) return PlaylistType.PRIVATE
|
||||||
return PlaylistType.PUBLIC
|
return PlaylistType.PUBLIC
|
||||||
|
@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
|||||||
data class PlaylistId(
|
data class PlaylistId(
|
||||||
var playlistId: String? = null,
|
var playlistId: String? = null,
|
||||||
var videoId: String? = null,
|
var videoId: String? = null,
|
||||||
|
var videoIds: List<String>? = null,
|
||||||
var newName: String? = null,
|
var newName: String? = null,
|
||||||
var index: Int = -1
|
var index: Int = -1
|
||||||
)
|
)
|
||||||
|
@ -49,8 +49,6 @@ object PreferenceKeys {
|
|||||||
const val CLEAR_CUSTOM_INSTANCES = "clearCustomInstances"
|
const val CLEAR_CUSTOM_INSTANCES = "clearCustomInstances"
|
||||||
const val LOGIN_REGISTER = "login_register"
|
const val LOGIN_REGISTER = "login_register"
|
||||||
const val DELETE_ACCOUNT = "delete_account"
|
const val DELETE_ACCOUNT = "delete_account"
|
||||||
const val IMPORT_SUBS = "import_from_yt"
|
|
||||||
const val EXPORT_SUBS = "export_subs"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player
|
* Player
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class ImportPlaylist(
|
||||||
|
val name: String? = null,
|
||||||
|
val type: String? = null,
|
||||||
|
val visibility: String? = null,
|
||||||
|
val videos: List<String> = listOf()
|
||||||
|
)
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.github.libretube.obj
|
||||||
|
|
||||||
|
data class ImportPlaylistFile(
|
||||||
|
val format: String? = null,
|
||||||
|
val version: Int? = null,
|
||||||
|
val playlists: List<ImportPlaylist>? = null
|
||||||
|
)
|
@ -1,6 +1,7 @@
|
|||||||
package com.github.libretube.ui.base
|
package com.github.libretube.ui.base
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.EditTextPreference
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@ -70,4 +71,10 @@ open class BasePreferenceFragment : PreferenceFragmentCompat() {
|
|||||||
else -> super.onDisplayPreferenceDialog(preference)
|
else -> super.onDisplayPreferenceDialog(preference)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Fragment?.runOnUiThread(action: () -> Unit) {
|
||||||
|
this ?: return
|
||||||
|
if (!isAdded) return // Fragment not attached to an Activity
|
||||||
|
activity?.runOnUiThread(action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,9 @@ class CreatePlaylistDialog(
|
|||||||
val listName = binding.playlistName.text.toString()
|
val listName = binding.playlistName.text.toString()
|
||||||
if (listName != "") {
|
if (listName != "") {
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
PlaylistsHelper.createPlaylist(listName, requireContext().applicationContext) {
|
PlaylistsHelper.createPlaylist(listName, requireContext().applicationContext)
|
||||||
onSuccess.invoke()
|
onSuccess.invoke()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show()
|
Toast.makeText(context, R.string.emptyPlaylistName, Toast.LENGTH_LONG).show()
|
||||||
|
@ -108,7 +108,7 @@ class HomeFragment : BaseFragment() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
makeVisible(binding.playlistsRV, binding.playlistsTV)
|
makeVisible(binding.playlistsRV, binding.playlistsTV)
|
||||||
binding.playlistsRV.layoutManager = LinearLayoutManager(context)
|
binding.playlistsRV.layoutManager = LinearLayoutManager(context)
|
||||||
binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), PlaylistsHelper.getPrivateType())
|
binding.playlistsRV.adapter = PlaylistsAdapter(playlists.toMutableList(), PlaylistsHelper.getPlaylistType())
|
||||||
binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
|
binding.playlistsRV.adapter?.registerAdapterDataObserver(object :
|
||||||
RecyclerView.AdapterDataObserver() {
|
RecyclerView.AdapterDataObserver() {
|
||||||
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {
|
||||||
|
@ -123,7 +123,7 @@ class LibraryFragment : BaseFragment() {
|
|||||||
|
|
||||||
val playlistsAdapter = PlaylistsAdapter(
|
val playlistsAdapter = PlaylistsAdapter(
|
||||||
playlists.toMutableList(),
|
playlists.toMutableList(),
|
||||||
PlaylistsHelper.getPrivateType()
|
PlaylistsHelper.getPlaylistType()
|
||||||
)
|
)
|
||||||
|
|
||||||
// listen for playlists to become deleted
|
// listen for playlists to become deleted
|
||||||
|
@ -99,7 +99,7 @@ class PlaylistFragment : BaseFragment() {
|
|||||||
binding.playlistScrollview.visibility = View.GONE
|
binding.playlistScrollview.visibility = View.GONE
|
||||||
lifecycleScope.launchWhenCreated {
|
lifecycleScope.launchWhenCreated {
|
||||||
val response = try {
|
val response = try {
|
||||||
PlaylistsHelper.getPlaylist(playlistType, playlistId!!)
|
PlaylistsHelper.getPlaylist(playlistId!!)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
println(e)
|
println(e)
|
||||||
Log.e(TAG(), "IOException, you might not have internet connection")
|
Log.e(TAG(), "IOException, you might not have internet connection")
|
||||||
|
@ -1,49 +1,18 @@
|
|||||||
package com.github.libretube.ui.preferences
|
package com.github.libretube.ui.preferences
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
import com.github.libretube.constants.PreferenceKeys
|
import com.github.libretube.constants.PreferenceKeys
|
||||||
import com.github.libretube.obj.BackupFile
|
|
||||||
import com.github.libretube.ui.activities.SettingsActivity
|
import com.github.libretube.ui.activities.SettingsActivity
|
||||||
import com.github.libretube.ui.base.BasePreferenceFragment
|
import com.github.libretube.ui.base.BasePreferenceFragment
|
||||||
import com.github.libretube.ui.dialogs.BackupDialog
|
|
||||||
import com.github.libretube.util.BackupHelper
|
|
||||||
import com.github.libretube.util.ImageHelper
|
import com.github.libretube.util.ImageHelper
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import java.time.LocalDate
|
|
||||||
import java.time.LocalTime
|
|
||||||
|
|
||||||
class AdvancedSettings : BasePreferenceFragment() {
|
class AdvancedSettings : BasePreferenceFragment() {
|
||||||
|
|
||||||
// backup and restore database
|
|
||||||
private lateinit var getBackupFile: ActivityResultLauncher<String>
|
|
||||||
private lateinit var createBackupFile: ActivityResultLauncher<String>
|
|
||||||
private var backupFile = BackupFile()
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
getBackupFile =
|
|
||||||
registerForActivityResult(
|
|
||||||
ActivityResultContracts.GetContent()
|
|
||||||
) { uri: Uri? ->
|
|
||||||
BackupHelper(requireContext()).restoreAdvancedBackup(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
createBackupFile = registerForActivityResult(
|
|
||||||
CreateDocument("application/json")
|
|
||||||
) { uri: Uri? ->
|
|
||||||
BackupHelper(requireContext()).advancedBackup(uri, backupFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.advanced_settings, rootKey)
|
setPreferencesFromResource(R.xml.advanced_settings, rootKey)
|
||||||
|
|
||||||
@ -61,22 +30,6 @@ class AdvancedSettings : BasePreferenceFragment() {
|
|||||||
showResetDialog()
|
showResetDialog()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val advancesBackup = findPreference<Preference>("backup")
|
|
||||||
advancesBackup?.setOnPreferenceClickListener {
|
|
||||||
BackupDialog {
|
|
||||||
backupFile = it
|
|
||||||
createBackupFile.launch(getBackupFileName())
|
|
||||||
}
|
|
||||||
.show(childFragmentManager, null)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val restoreAdvancedBackup = findPreference<Preference>("restore")
|
|
||||||
restoreAdvancedBackup?.setOnPreferenceClickListener {
|
|
||||||
getBackupFile.launch("application/json")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showResetDialog() {
|
private fun showResetDialog() {
|
||||||
@ -95,9 +48,4 @@ class AdvancedSettings : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getBackupFileName(): String {
|
|
||||||
val time = LocalTime.now().toString().split(".").firstOrNull()
|
|
||||||
return "libretube-backup-${LocalDate.now()}-$time.json"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package com.github.libretube.ui.preferences
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.obj.BackupFile
|
||||||
|
import com.github.libretube.ui.base.BasePreferenceFragment
|
||||||
|
import com.github.libretube.ui.dialogs.BackupDialog
|
||||||
|
import com.github.libretube.util.BackupHelper
|
||||||
|
import com.github.libretube.util.ImportHelper
|
||||||
|
import java.time.LocalDate
|
||||||
|
import java.time.LocalTime
|
||||||
|
|
||||||
|
class BackupRestoreSettings : BasePreferenceFragment() {
|
||||||
|
|
||||||
|
// backup and restore database
|
||||||
|
private lateinit var getBackupFile: ActivityResultLauncher<String>
|
||||||
|
private lateinit var createBackupFile: ActivityResultLauncher<String>
|
||||||
|
private var backupFile = BackupFile()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result listeners for importing and exporting subscriptions
|
||||||
|
*/
|
||||||
|
private lateinit var getSubscriptionsFile: ActivityResultLauncher<String>
|
||||||
|
private lateinit var createSubscriptionsFile: ActivityResultLauncher<String>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* result listeners for importing and exporting playlists
|
||||||
|
*/
|
||||||
|
private lateinit var getPlaylistsFile: ActivityResultLauncher<String>
|
||||||
|
private lateinit var createPlaylistsFile: ActivityResultLauncher<String>
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
getSubscriptionsFile =
|
||||||
|
registerForActivityResult(
|
||||||
|
ActivityResultContracts.GetContent()
|
||||||
|
) { uri ->
|
||||||
|
ImportHelper(requireActivity()).importSubscriptions(uri)
|
||||||
|
}
|
||||||
|
createSubscriptionsFile = registerForActivityResult(
|
||||||
|
CreateDocument("application/json")
|
||||||
|
) { uri ->
|
||||||
|
ImportHelper(requireActivity()).exportSubscriptions(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlaylistsFile = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
|
ImportHelper(requireActivity()).importPlaylists(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlaylistsFile = registerForActivityResult(
|
||||||
|
CreateDocument("application/json")
|
||||||
|
) { uri ->
|
||||||
|
ImportHelper(requireActivity()).exportPlaylists(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
getBackupFile =
|
||||||
|
registerForActivityResult(
|
||||||
|
ActivityResultContracts.GetContent()
|
||||||
|
) { uri: Uri? ->
|
||||||
|
BackupHelper(requireContext()).restoreAdvancedBackup(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
createBackupFile = registerForActivityResult(
|
||||||
|
CreateDocument("application/json")
|
||||||
|
) { uri: Uri? ->
|
||||||
|
BackupHelper(requireContext()).advancedBackup(uri, backupFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
setPreferencesFromResource(R.xml.import_export_settings, rootKey)
|
||||||
|
|
||||||
|
val importSubscriptions = findPreference<Preference>("import_subscriptions")
|
||||||
|
importSubscriptions?.setOnPreferenceClickListener {
|
||||||
|
getSubscriptionsFile.launch("*/*")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val exportSubscriptions = findPreference<Preference>("export_subscriptions")
|
||||||
|
exportSubscriptions?.setOnPreferenceClickListener {
|
||||||
|
createSubscriptionsFile.launch("subscriptions.json")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val importPlaylists = findPreference<Preference>("import_playlists")
|
||||||
|
importPlaylists?.setOnPreferenceClickListener {
|
||||||
|
getPlaylistsFile.launch("*/*")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val exportPlaylists = findPreference<Preference>("export_playlists")
|
||||||
|
exportPlaylists?.setOnPreferenceClickListener {
|
||||||
|
createPlaylistsFile.launch("playlists.json")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val advancesBackup = findPreference<Preference>("backup")
|
||||||
|
advancesBackup?.setOnPreferenceClickListener {
|
||||||
|
BackupDialog {
|
||||||
|
backupFile = it
|
||||||
|
createBackupFile.launch(getBackupFileName())
|
||||||
|
}
|
||||||
|
.show(childFragmentManager, null)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val restoreAdvancedBackup = findPreference<Preference>("restore")
|
||||||
|
restoreAdvancedBackup?.setOnPreferenceClickListener {
|
||||||
|
getBackupFile.launch("application/json")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBackupFileName(): String {
|
||||||
|
val time = LocalTime.now().toString().split(".").firstOrNull()
|
||||||
|
return "libretube-backup-${LocalDate.now()}-$time.json"
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,7 @@
|
|||||||
package com.github.libretube.ui.preferences
|
package com.github.libretube.ui.preferences
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
@ -22,33 +17,10 @@ import com.github.libretube.ui.dialogs.CustomInstanceDialog
|
|||||||
import com.github.libretube.ui.dialogs.DeleteAccountDialog
|
import com.github.libretube.ui.dialogs.DeleteAccountDialog
|
||||||
import com.github.libretube.ui.dialogs.LoginDialog
|
import com.github.libretube.ui.dialogs.LoginDialog
|
||||||
import com.github.libretube.ui.dialogs.LogoutDialog
|
import com.github.libretube.ui.dialogs.LogoutDialog
|
||||||
import com.github.libretube.util.ImportHelper
|
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
|
|
||||||
class InstanceSettings : BasePreferenceFragment() {
|
class InstanceSettings : BasePreferenceFragment() {
|
||||||
|
|
||||||
/**
|
|
||||||
* result listeners for importing and exporting subscriptions
|
|
||||||
*/
|
|
||||||
private lateinit var getContent: ActivityResultLauncher<String>
|
|
||||||
private lateinit var createFile: ActivityResultLauncher<String>
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
getContent =
|
|
||||||
registerForActivityResult(
|
|
||||||
ActivityResultContracts.GetContent()
|
|
||||||
) { uri: Uri? ->
|
|
||||||
ImportHelper(requireActivity()).importSubscriptions(uri)
|
|
||||||
}
|
|
||||||
createFile = registerForActivityResult(
|
|
||||||
CreateDocument("application/json")
|
|
||||||
) { uri: Uri? ->
|
|
||||||
ImportHelper(requireActivity()).exportSubscriptions(uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.instance_settings, rootKey)
|
setPreferencesFromResource(R.xml.instance_settings, rootKey)
|
||||||
|
|
||||||
@ -138,19 +110,6 @@ class InstanceSettings : BasePreferenceFragment() {
|
|||||||
newFragment.show(childFragmentManager, DeleteAccountDialog::class.java.name)
|
newFragment.show(childFragmentManager, DeleteAccountDialog::class.java.name)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val importSubscriptions = findPreference<Preference>(PreferenceKeys.IMPORT_SUBS)
|
|
||||||
importSubscriptions?.setOnPreferenceClickListener {
|
|
||||||
// check StorageAccess
|
|
||||||
getContent.launch("*/*")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
val exportSubscriptions = findPreference<Preference>(PreferenceKeys.EXPORT_SUBS)
|
|
||||||
exportSubscriptions?.setOnPreferenceClickListener {
|
|
||||||
createFile.launch("subscriptions.json")
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initCustomInstances(instancePref: ListPreference) {
|
private fun initCustomInstances(instancePref: ListPreference) {
|
||||||
@ -201,10 +160,4 @@ class InstanceSettings : BasePreferenceFragment() {
|
|||||||
PreferenceHelper.setToken("")
|
PreferenceHelper.setToken("")
|
||||||
Toast.makeText(context, getString(R.string.loggedout), Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, getString(R.string.loggedout), Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Fragment?.runOnUiThread(action: () -> Unit) {
|
|
||||||
this ?: return
|
|
||||||
if (!isAdded) return // Fragment not attached to an Activity
|
|
||||||
activity?.runOnUiThread(action)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -23,65 +23,52 @@ class MainSettings : BasePreferenceFragment() {
|
|||||||
|
|
||||||
val general = findPreference<Preference>("general")
|
val general = findPreference<Preference>("general")
|
||||||
general?.setOnPreferenceClickListener {
|
general?.setOnPreferenceClickListener {
|
||||||
val newFragment = GeneralSettings()
|
navigateToSettingsFragment(GeneralSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val instance = findPreference<Preference>("instance")
|
val instance = findPreference<Preference>("instance")
|
||||||
instance?.setOnPreferenceClickListener {
|
instance?.setOnPreferenceClickListener {
|
||||||
val newFragment = InstanceSettings()
|
navigateToSettingsFragment(InstanceSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val appearance = findPreference<Preference>("appearance")
|
val appearance = findPreference<Preference>("appearance")
|
||||||
appearance?.setOnPreferenceClickListener {
|
appearance?.setOnPreferenceClickListener {
|
||||||
val newFragment = AppearanceSettings()
|
navigateToSettingsFragment(AppearanceSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val sponsorBlock = findPreference<Preference>("sponsorblock")
|
val sponsorBlock = findPreference<Preference>("sponsorblock")
|
||||||
sponsorBlock?.setOnPreferenceClickListener {
|
sponsorBlock?.setOnPreferenceClickListener {
|
||||||
val newFragment = SponsorBlockSettings()
|
navigateToSettingsFragment(SponsorBlockSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val player = findPreference<Preference>("player")
|
val player = findPreference<Preference>("player")
|
||||||
player?.setOnPreferenceClickListener {
|
player?.setOnPreferenceClickListener {
|
||||||
val newFragment = PlayerSettings()
|
navigateToSettingsFragment(PlayerSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val audioVideo = findPreference<Preference>("audio_video")
|
val audioVideo = findPreference<Preference>("audio_video")
|
||||||
audioVideo?.setOnPreferenceClickListener {
|
audioVideo?.setOnPreferenceClickListener {
|
||||||
val newFragment = AudioVideoSettings()
|
navigateToSettingsFragment(AudioVideoSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val history = findPreference<Preference>("history")
|
val history = findPreference<Preference>("history")
|
||||||
history?.setOnPreferenceClickListener {
|
history?.setOnPreferenceClickListener {
|
||||||
val newFragment = HistorySettings()
|
navigateToSettingsFragment(HistorySettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val notifications = findPreference<Preference>("notifications")
|
val notifications = findPreference<Preference>("notifications")
|
||||||
notifications?.setOnPreferenceClickListener {
|
notifications?.setOnPreferenceClickListener {
|
||||||
val newFragment = NotificationSettings()
|
navigateToSettingsFragment(NotificationSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
}
|
||||||
true
|
|
||||||
|
val backupRestore = findPreference<Preference>("backup_restore")
|
||||||
|
backupRestore?.setOnPreferenceClickListener {
|
||||||
|
navigateToSettingsFragment(BackupRestoreSettings())
|
||||||
}
|
}
|
||||||
|
|
||||||
val advanced = findPreference<Preference>("advanced")
|
val advanced = findPreference<Preference>("advanced")
|
||||||
advanced?.setOnPreferenceClickListener {
|
advanced?.setOnPreferenceClickListener {
|
||||||
val newFragment = AdvancedSettings()
|
navigateToSettingsFragment(AdvancedSettings())
|
||||||
navigateToSettingsFragment(newFragment)
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val update = findPreference<Preference>("update")
|
val update = findPreference<Preference>("update")
|
||||||
@ -131,9 +118,10 @@ class MainSettings : BasePreferenceFragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun navigateToSettingsFragment(newFragment: Fragment) {
|
private fun navigateToSettingsFragment(newFragment: Fragment): Boolean {
|
||||||
parentFragmentManager.beginTransaction()
|
parentFragmentManager.beginTransaction()
|
||||||
.replace(R.id.settings, newFragment)
|
.replace(R.id.settings, newFragment)
|
||||||
.commitNow()
|
.commitNow()
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper
|
import com.fasterxml.jackson.databind.ObjectMapper
|
||||||
import com.github.libretube.R
|
import com.github.libretube.R
|
||||||
|
import com.github.libretube.api.PlaylistsHelper
|
||||||
import com.github.libretube.api.RetrofitInstance
|
import com.github.libretube.api.RetrofitInstance
|
||||||
import com.github.libretube.api.SubscriptionHelper
|
import com.github.libretube.api.SubscriptionHelper
|
||||||
import com.github.libretube.extensions.TAG
|
import com.github.libretube.extensions.TAG
|
||||||
import com.github.libretube.extensions.toastFromMainThread
|
import com.github.libretube.extensions.toastFromMainThread
|
||||||
|
import com.github.libretube.obj.ImportPlaylistFile
|
||||||
import com.github.libretube.obj.NewPipeSubscription
|
import com.github.libretube.obj.NewPipeSubscription
|
||||||
import com.github.libretube.obj.NewPipeSubscriptions
|
import com.github.libretube.obj.NewPipeSubscriptions
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -36,12 +38,10 @@ class ImportHelper(
|
|||||||
}
|
}
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
Toast.makeText(
|
activity.toastFromMainThread(
|
||||||
activity,
|
|
||||||
activity.getString(R.string.unsupported_file_format) +
|
activity.getString(R.string.unsupported_file_format) +
|
||||||
" (${activity.contentResolver.getType(uri)}",
|
" (${activity.contentResolver.getType(uri)}"
|
||||||
Toast.LENGTH_SHORT
|
)
|
||||||
).show()
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG(), e.toString())
|
Log.e(TAG(), e.toString())
|
||||||
Toast.makeText(activity, e.localizedMessage, Toast.LENGTH_SHORT).show()
|
Toast.makeText(activity, e.localizedMessage, Toast.LENGTH_SHORT).show()
|
||||||
@ -55,12 +55,7 @@ class ImportHelper(
|
|||||||
return when (val fileType = activity.contentResolver.getType(uri)) {
|
return when (val fileType = activity.contentResolver.getType(uri)) {
|
||||||
"application/json", "application/octet-stream" -> {
|
"application/json", "application/octet-stream" -> {
|
||||||
// NewPipe subscriptions format
|
// NewPipe subscriptions format
|
||||||
val mapper = ObjectMapper()
|
val subscriptions = ObjectMapper().readValue(uri.readText(), NewPipeSubscriptions::class.java)
|
||||||
val json = activity.contentResolver.openInputStream(uri)?.use {
|
|
||||||
it.bufferedReader().use { reader -> reader.readText() }
|
|
||||||
}.orEmpty()
|
|
||||||
|
|
||||||
val subscriptions = mapper.readValue(json, NewPipeSubscriptions::class.java)
|
|
||||||
subscriptions.subscriptions.orEmpty().map {
|
subscriptions.subscriptions.orEmpty().map {
|
||||||
it.url!!.replace("https://www.youtube.com/channel/", "")
|
it.url!!.replace("https://www.youtube.com/channel/", "")
|
||||||
}
|
}
|
||||||
@ -84,40 +79,83 @@ class ImportHelper(
|
|||||||
*/
|
*/
|
||||||
fun exportSubscriptions(uri: Uri?) {
|
fun exportSubscriptions(uri: Uri?) {
|
||||||
if (uri == null) return
|
if (uri == null) return
|
||||||
try {
|
runBlocking {
|
||||||
val mapper = ObjectMapper()
|
val subs = if (PreferenceHelper.getToken() != "") {
|
||||||
val token = PreferenceHelper.getToken()
|
RetrofitInstance.authApi.subscriptions(PreferenceHelper.getToken())
|
||||||
runBlocking {
|
} else {
|
||||||
val subs = if (token != "") {
|
RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
||||||
RetrofitInstance.authApi.subscriptions(token)
|
SubscriptionHelper.getFormattedLocalSubscriptions()
|
||||||
} else {
|
)
|
||||||
RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
}
|
||||||
SubscriptionHelper.getFormattedLocalSubscriptions()
|
val newPipeChannels = mutableListOf<NewPipeSubscription>()
|
||||||
)
|
subs.forEach {
|
||||||
}
|
newPipeChannels += NewPipeSubscription(
|
||||||
val newPipeChannels = mutableListOf<NewPipeSubscription>()
|
name = it.name,
|
||||||
subs.forEach {
|
service_id = 0,
|
||||||
newPipeChannels += NewPipeSubscription(
|
url = "https://www.youtube.com" + it.url
|
||||||
name = it.name,
|
)
|
||||||
service_id = 0,
|
}
|
||||||
url = "https://www.youtube.com" + it.url
|
|
||||||
)
|
val newPipeSubscriptions = NewPipeSubscriptions(
|
||||||
}
|
subscriptions = newPipeChannels
|
||||||
|
)
|
||||||
val newPipeSubscriptions = NewPipeSubscriptions(
|
|
||||||
subscriptions = newPipeChannels
|
uri.write(newPipeSubscriptions)
|
||||||
|
|
||||||
|
activity.toastFromMainThread(R.string.exportsuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import Playlists
|
||||||
|
*/
|
||||||
|
fun importPlaylists(uri: Uri?) {
|
||||||
|
if (uri == null) return
|
||||||
|
|
||||||
|
val playlistFile = ObjectMapper().readValue(uri.readText(), ImportPlaylistFile::class.java)
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
playlistFile.playlists?.let {
|
||||||
|
PlaylistsHelper.importPlaylists(activity, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.toastFromMainThread(R.string.success)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export Playlists
|
||||||
|
*/
|
||||||
|
fun exportPlaylists(uri: Uri?) {
|
||||||
|
if (uri == null) return
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
val playlists = PlaylistsHelper.exportPlaylists()
|
||||||
|
val playlistFile = ImportPlaylistFile(
|
||||||
|
format = "Piped",
|
||||||
|
version = 1,
|
||||||
|
playlists = playlists
|
||||||
|
)
|
||||||
|
|
||||||
|
uri.write(playlistFile)
|
||||||
|
|
||||||
|
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)
|
||||||
)
|
)
|
||||||
|
|
||||||
val data = mapper.writeValueAsBytes(newPipeSubscriptions)
|
|
||||||
|
|
||||||
activity.contentResolver.openFileDescriptor(uri, "w")?.use {
|
|
||||||
FileOutputStream(it.fileDescriptor).use { fileOutputStream ->
|
|
||||||
fileOutputStream.write(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,8 +110,8 @@ object PlayingQueue {
|
|||||||
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) {
|
fun insertPlaylist(playlistId: String, newCurrentStream: StreamItem) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val playlistType = PlaylistsHelper.getPrivateType(playlistId)
|
val playlistType = PlaylistsHelper.getPlaylistType(playlistId)
|
||||||
val playlist = PlaylistsHelper.getPlaylist(playlistType, playlistId)
|
val playlist = PlaylistsHelper.getPlaylist(playlistId)
|
||||||
add(
|
add(
|
||||||
*playlist.relatedStreams
|
*playlist.relatedStreams
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
@ -17,7 +17,6 @@ import com.github.libretube.extensions.toID
|
|||||||
import com.github.libretube.ui.activities.MainActivity
|
import com.github.libretube.ui.activities.MainActivity
|
||||||
import com.github.libretube.ui.views.TimePickerPreference
|
import com.github.libretube.ui.views.TimePickerPreference
|
||||||
import com.github.libretube.util.PreferenceHelper
|
import com.github.libretube.util.PreferenceHelper
|
||||||
import kotlinx.coroutines.async
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
|
|
||||||
@ -82,12 +81,9 @@ class NotificationWorker(appContext: Context, parameters: WorkerParameters) :
|
|||||||
var success = true
|
var success = true
|
||||||
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val task = async {
|
|
||||||
SubscriptionHelper.getFeed()
|
|
||||||
}
|
|
||||||
// fetch the users feed
|
// fetch the users feed
|
||||||
val videoFeed = try {
|
val videoFeed = try {
|
||||||
task.await()
|
SubscriptionHelper.getFeed()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
success = false
|
success = false
|
||||||
return@runBlocking
|
return@runBlocking
|
||||||
|
@ -403,6 +403,11 @@
|
|||||||
<string name="double_tap_seek_summary">Tap twice at the left or right to rewind or forward the player position.</string>
|
<string name="double_tap_seek_summary">Tap twice at the left or right to rewind or forward the player position.</string>
|
||||||
<string name="all_caught_up">You\'re all caught up</string>
|
<string name="all_caught_up">You\'re all caught up</string>
|
||||||
<string name="all_caught_up_summary">You\'ve seen all new videos</string>
|
<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="app_backup">App Backup</string>
|
||||||
|
<string name="backup_restore_summary">Import & export subscriptions, playlists, …</string>
|
||||||
|
<string name="exportsuccess">Successfully exported!</string>
|
||||||
|
|
||||||
<!-- Notification channel strings -->
|
<!-- Notification channel strings -->
|
||||||
<string name="download_channel_name">Download Service</string>
|
<string name="download_channel_name">Download Service</string>
|
||||||
|
@ -39,20 +39,6 @@
|
|||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/backup_restore">
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:icon="@drawable/ic_backup"
|
|
||||||
app:key="backup"
|
|
||||||
app:title="@string/backup" />
|
|
||||||
|
|
||||||
<Preference
|
|
||||||
android:icon="@drawable/ic_restore"
|
|
||||||
app:key="restore"
|
|
||||||
app:title="@string/restore" />
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
|
||||||
|
|
||||||
<PreferenceCategory app:title="@string/misc">
|
<PreferenceCategory app:title="@string/misc">
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
|
48
app/src/main/res/xml/import_export_settings.xml
Normal file
48
app/src/main/res/xml/import_export_settings.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/subscriptions">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_download_filled"
|
||||||
|
android:summary="@string/import_from_yt_summary"
|
||||||
|
app:key="import_subscriptions"
|
||||||
|
app:title="@string/import_from_yt" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_upload"
|
||||||
|
app:key="export_subscriptions"
|
||||||
|
app:title="@string/export_subscriptions" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/playlists">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_download_filled"
|
||||||
|
app:key="import_playlists"
|
||||||
|
app:title="@string/import_playlists" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_upload"
|
||||||
|
app:key="export_playlists"
|
||||||
|
app:title="@string/export_playlists" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
<PreferenceCategory app:title="@string/app_backup">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_backup"
|
||||||
|
app:key="backup"
|
||||||
|
app:title="@string/backup" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_restore"
|
||||||
|
app:key="restore"
|
||||||
|
app:title="@string/restore" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
|
|
||||||
|
</PreferenceScreen>
|
@ -50,6 +50,12 @@
|
|||||||
app:key="notifications"
|
app:key="notifications"
|
||||||
app:title="@string/notifications" />
|
app:title="@string/notifications" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:icon="@drawable/ic_backup"
|
||||||
|
app:key="backup_restore"
|
||||||
|
app:summary="@string/backup_restore_summary"
|
||||||
|
app:title="@string/backup_restore" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_list"
|
android:icon="@drawable/ic_list"
|
||||||
app:key="advanced"
|
app:key="advanced"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user