Skip to content

Basic functionality for export/import playlists#112

Open
m-tki wants to merge 1 commit intobrahmkshatriya:mainfrom
m-tki:hls-live
Open

Basic functionality for export/import playlists#112
m-tki wants to merge 1 commit intobrahmkshatriya:mainfrom
m-tki:hls-live

Conversation

@m-tki
Copy link
Contributor

@m-tki m-tki commented Aug 18, 2025

This pr adds basic functionality for export/import playlists in Unified extension (export/import all playlists without selecting)

@brahmkshatriya brahmkshatriya moved this to Backlog in Echo Road Map Aug 25, 2025
@brahmkshatriya brahmkshatriya moved this from Backlog to Ready in Echo Road Map Aug 25, 2025
Copy link
Owner

@brahmkshatriya brahmkshatriya left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR, I hope you can make the changes I requested

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this required, thought you could just use setOnPreferenceClickedListener or something

Comment on lines +209 to +250
private fun exportPlaylists(client: ExtensionClient) {
val context = preferenceManager.context
if (client is PlaylistEditClient) {
PickerActivity.openFolder(context) { uri ->
val directory = DocumentFile.fromTreeUri(context, uri) ?: run {
throw Exception("Failed to get directory from URI")
}

val file = directory.createFile("application/json", "playlists") ?: run {
throw Exception("Failed to create file")
}

viewModel.viewModelScope.launch {
try {
context.contentResolver.openOutputStream(file.uri)?.use { outputStream ->
outputStream.write(client.serializePlaylists().toByteArray())
}
} catch (e: Exception) {
file.delete()
throw e
}
}
}
}
}

private fun importPlaylists(client: ExtensionClient) {
val context = preferenceManager.context
if (client is PlaylistEditClient) {
PickerActivity.openFile(context) { uri ->
context.contentResolver.openInputStream(uri)?.use { inputStream ->
inputStream.bufferedReader().use { reader ->
val text = reader.readText()
viewModel.viewModelScope.launch {
client.deserializePlaylists(text)
}
}
} ?: throw Exception("Cannot open input stream for $uri")
}
}
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move all this code to the viewmodel itself, and instead of passing the client, use the extension from extensionFlow

Comment on lines +11 to +14
sealed interface PickerType {
object Folder : PickerType
object File : PickerType
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use an enum class instead

Comment on lines +46 to +47
this.callback = callback
this.pickerType = pickerType
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should put the picker type data inside a Bundle,
and the callback should removed

Comment on lines +55 to +70
private val pickerLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
try {
when {
result.resultCode == RESULT_OK && result.data?.data != null -> {
callback?.onSelected(result.data!!.data!!)
}
else -> callback?.onCancelled()
}
} finally {
callback = null
pickerType = null
finish()
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be created inside open function, and use runCatching instead of try, catch, finally

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to put the code in open function but AI gives me the following warning
registerForActivityResult must be called during the component's initialization phase (before or during onCreate), not from a static context like the companion object. The current implementation will likely crash because we're trying to register for activity result from a non-activity context.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Try using this

fun <I, O> ComponentActivity.registerActivityResultLauncher(

Comment on lines +93 to +98
override fun onDestroy() {
callback?.onCancelled()
callback = null
pickerType = null
super.onDestroy()
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wont be required after the pickerLaucher changes

screen.addPreference(infoPreference)

val client = extension.instance.value().getOrThrow()
if (extension.id == UnifiedExtension.metadata.id) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why limit it to unified extension?

)
screen.addPreference(infoPreference)

val client = extension.instance.value().getOrThrow()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove, client is not needed here

@brahmkshatriya brahmkshatriya moved this from Ready to In progress in Echo Road Map Aug 25, 2025
@brahmkshatriya brahmkshatriya moved this from In progress to In review in Echo Road Map Aug 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants