mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-28 16:00:31 +05:30
Add UI functionality for importing and exporting playlists
This commit is contained in:
parent
8ce809d30f
commit
c1793691ed
@ -14,6 +14,7 @@ import com.github.libretube.extensions.awaitQuery
|
|||||||
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 retrofit2.HttpException
|
import retrofit2.HttpException
|
||||||
@ -175,6 +176,14 @@ object PlaylistsHelper {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun importPlaylists(playlist: List<ImportPlaylist>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun exportPlaylists(): List<ImportPlaylist> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
fun getPrivateType(): PlaylistType {
|
fun getPrivateType(): PlaylistType {
|
||||||
return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL
|
return if (loggedIn()) PlaylistType.PRIVATE else PlaylistType.LOCAL
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -7,7 +7,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||||
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.obj.BackupFile
|
import com.github.libretube.obj.BackupFile
|
||||||
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.ui.dialogs.BackupDialog
|
||||||
@ -29,19 +28,35 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
|||||||
private lateinit var getSubscriptionsFile: ActivityResultLauncher<String>
|
private lateinit var getSubscriptionsFile: ActivityResultLauncher<String>
|
||||||
private lateinit var createSubscriptionsFile: 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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
getSubscriptionsFile =
|
getSubscriptionsFile =
|
||||||
registerForActivityResult(
|
registerForActivityResult(
|
||||||
ActivityResultContracts.GetContent()
|
ActivityResultContracts.GetContent()
|
||||||
) { uri: Uri? ->
|
) { uri ->
|
||||||
ImportHelper(requireActivity()).importSubscriptions(uri)
|
ImportHelper(requireActivity()).importSubscriptions(uri)
|
||||||
}
|
}
|
||||||
createSubscriptionsFile = registerForActivityResult(
|
createSubscriptionsFile = registerForActivityResult(
|
||||||
CreateDocument("application/json")
|
CreateDocument("application/json")
|
||||||
) { uri: Uri? ->
|
) { uri ->
|
||||||
ImportHelper(requireActivity()).exportSubscriptions(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 =
|
getBackupFile =
|
||||||
registerForActivityResult(
|
registerForActivityResult(
|
||||||
ActivityResultContracts.GetContent()
|
ActivityResultContracts.GetContent()
|
||||||
@ -61,19 +76,30 @@ class BackupRestoreSettings : BasePreferenceFragment() {
|
|||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
setPreferencesFromResource(R.xml.import_export_settings, rootKey)
|
setPreferencesFromResource(R.xml.import_export_settings, rootKey)
|
||||||
|
|
||||||
val importSubscriptions = findPreference<Preference>(PreferenceKeys.IMPORT_SUBS)
|
val importSubscriptions = findPreference<Preference>("import_subscriptions")
|
||||||
importSubscriptions?.setOnPreferenceClickListener {
|
importSubscriptions?.setOnPreferenceClickListener {
|
||||||
// check StorageAccess
|
|
||||||
getSubscriptionsFile.launch("*/*")
|
getSubscriptionsFile.launch("*/*")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
val exportSubscriptions = findPreference<Preference>(PreferenceKeys.EXPORT_SUBS)
|
val exportSubscriptions = findPreference<Preference>("export_subscriptions")
|
||||||
exportSubscriptions?.setOnPreferenceClickListener {
|
exportSubscriptions?.setOnPreferenceClickListener {
|
||||||
createSubscriptionsFile.launch("subscriptions.json")
|
createSubscriptionsFile.launch("subscriptions.json")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val importPlaylists = findPreference<Preference>("import_playlists")
|
||||||
|
importPlaylists?.setOnPreferenceClickListener {
|
||||||
|
getPlaylistsFile.launch("*/*")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
val exportPlaylists = findPreference<Preference>("export_playlists")
|
||||||
|
exportPlaylists?.setOnPreferenceClickListener {
|
||||||
|
createPlaylistsFile.launch("subscriptions.json")
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
val advancesBackup = findPreference<Preference>("backup")
|
val advancesBackup = findPreference<Preference>("backup")
|
||||||
advancesBackup?.setOnPreferenceClickListener {
|
advancesBackup?.setOnPreferenceClickListener {
|
||||||
BackupDialog {
|
BackupDialog {
|
||||||
|
@ -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,12 +79,9 @@ class ImportHelper(
|
|||||||
*/
|
*/
|
||||||
fun exportSubscriptions(uri: Uri?) {
|
fun exportSubscriptions(uri: Uri?) {
|
||||||
if (uri == null) return
|
if (uri == null) return
|
||||||
try {
|
|
||||||
val mapper = ObjectMapper()
|
|
||||||
val token = PreferenceHelper.getToken()
|
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val subs = if (token != "") {
|
val subs = if (PreferenceHelper.getToken() != "") {
|
||||||
RetrofitInstance.authApi.subscriptions(token)
|
RetrofitInstance.authApi.subscriptions(PreferenceHelper.getToken())
|
||||||
} else {
|
} else {
|
||||||
RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
RetrofitInstance.authApi.unauthenticatedSubscriptions(
|
||||||
SubscriptionHelper.getFormattedLocalSubscriptions()
|
SubscriptionHelper.getFormattedLocalSubscriptions()
|
||||||
@ -108,16 +100,64 @@ class ImportHelper(
|
|||||||
subscriptions = newPipeChannels
|
subscriptions = newPipeChannels
|
||||||
)
|
)
|
||||||
|
|
||||||
val data = mapper.writeValueAsBytes(newPipeSubscriptions)
|
uri.write(newPipeSubscriptions)
|
||||||
|
|
||||||
activity.contentResolver.openFileDescriptor(uri, "w")?.use {
|
activity.toastFromMainThread(R.string.exportsuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import Playlists
|
||||||
|
*/
|
||||||
|
fun importPlaylists(uri: Uri?) {
|
||||||
|
if (uri == null) return
|
||||||
|
|
||||||
|
val playlistFile = ObjectMapper().readValue(uri.readText(), ImportPlaylistFile::class.java)
|
||||||
|
|
||||||
|
playlistFile.playlists.orEmpty().forEach {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
playlistFile.playlists?.let {
|
||||||
|
PlaylistsHelper.importPlaylists(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activity.toastFromMainThread(R.string.importsuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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(it.fileDescriptor).use { fileOutputStream ->
|
||||||
fileOutputStream.write(data)
|
fileOutputStream.write(
|
||||||
|
ObjectMapper().writeValueAsBytes(text)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -406,7 +406,8 @@
|
|||||||
<string name="import_playlists">Import playlists</string>
|
<string name="import_playlists">Import playlists</string>
|
||||||
<string name="export_playlists">Export playlists</string>
|
<string name="export_playlists">Export playlists</string>
|
||||||
<string name="app_backup">App Backup</string>
|
<string name="app_backup">App Backup</string>
|
||||||
<string name="backup_restore_summary">Import & export subscriptions, playlists, ...</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>
|
||||||
|
@ -7,12 +7,12 @@
|
|||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_download_filled"
|
android:icon="@drawable/ic_download_filled"
|
||||||
android:summary="@string/import_from_yt_summary"
|
android:summary="@string/import_from_yt_summary"
|
||||||
app:key="import_from_yt"
|
app:key="import_subscriptions"
|
||||||
app:title="@string/import_from_yt" />
|
app:title="@string/import_from_yt" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_upload"
|
android:icon="@drawable/ic_upload"
|
||||||
app:key="export_subs"
|
app:key="export_subscriptions"
|
||||||
app:title="@string/export_subscriptions" />
|
app:title="@string/export_subscriptions" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
@ -22,12 +22,12 @@
|
|||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_download_filled"
|
android:icon="@drawable/ic_download_filled"
|
||||||
android:summary="@string/import_playlists"
|
android:summary="@string/import_playlists"
|
||||||
app:key="import_from_yt"
|
app:key="import_playlists"
|
||||||
app:title="@string/import_from_yt" />
|
app:title="@string/import_from_yt" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:icon="@drawable/ic_upload"
|
android:icon="@drawable/ic_upload"
|
||||||
app:key="export_subs"
|
app:key="export_playlists"
|
||||||
app:title="@string/export_playlists" />
|
app:title="@string/export_playlists" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user