Add UI functionality for importing and exporting playlists

This commit is contained in:
Bnyro 2022-12-01 14:09:57 +01:00
parent 8ce809d30f
commit c1793691ed
6 changed files with 127 additions and 53 deletions

View File

@ -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
} }

View File

@ -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

View File

@ -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 {

View File

@ -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()
}
}
} }

View File

@ -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 &amp; export subscriptions, playlists, ...</string> <string name="backup_restore_summary">Import &amp; 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>

View File

@ -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>