diff --git a/CHANGELOG.md b/CHANGELOG.md index 85ceb086ea3..886aa0b2f06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,15 @@ * New Features: * Added Tasker integration with "Play Filter" and "Control Playback" actions. ([#415](https://github.com/Automattic/pocket-casts-android/pull/431)). - * Fixed background color for screens using the compose theme - ([#432](https://github.com/Automattic/pocket-casts-android/pull/432)). + * Import OPML from a URL + ([#482](https://github.com/Automattic/pocket-casts-android/pull/482)). * Bug Fixes: * Fixed Help & Feedback buttons being hidden when using text zoom. ([#446](https://github.com/Automattic/pocket-casts-android/pull/446)). * Fixed when system bar didn't disappear on full screen video player ([#461](https://github.com/Automattic/pocket-casts-android/pull/461)). + * Fixed background color for screens using the compose theme + ([#432](https://github.com/Automattic/pocket-casts-android/pull/432)). 7.25 ----- diff --git a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/ExportSettingsFragment.kt b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/ExportSettingsFragment.kt index 5319578640a..0de59dd6e1f 100644 --- a/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/ExportSettingsFragment.kt +++ b/modules/features/settings/src/main/java/au/com/shiftyjelly/pocketcasts/settings/ExportSettingsFragment.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.View +import androidx.preference.EditTextPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import au.com.shiftyjelly.pocketcasts.preferences.Settings @@ -45,6 +46,14 @@ class ExportSettingsFragment : PreferenceFragmentCompat() { true } + findPreference("importPodcastsByUrl")?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val url = newValue.toString() + if (url.isNotBlank()) { + OpmlImportTask.run(url, requireActivity()) + } + false + } + findPreference("exportSendEmail")?.onPreferenceClickListener = Preference.OnPreferenceClickListener { exporter = OpmlExporter(this@ExportSettingsFragment, serverManager, podcastManager, settings, activity).apply { sendEmail() diff --git a/modules/features/settings/src/main/res/xml/preferences_export.xml b/modules/features/settings/src/main/res/xml/preferences_export.xml index 6d9ef239c36..5c98ff3cf22 100644 --- a/modules/features/settings/src/main/res/xml/preferences_export.xml +++ b/modules/features/settings/src/main/res/xml/preferences_export.xml @@ -7,6 +7,10 @@ android:key="importPodcasts" android:summary="@string/settings_import_file_summary" android:title="@string/settings_import_file" /> + Choose an OPML file. Select file If you have podcast subscriptions in another app or service, Pocket Casts can import them from an OPML file. + By URL + Import your podcasts from an OPML file using a URL. %1$d of %2$d podcasts Stop Import OPML diff --git a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/opml/OpmlImportTask.kt b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/opml/OpmlImportTask.kt index c385327b490..3dcb40db1fe 100644 --- a/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/opml/OpmlImportTask.kt +++ b/modules/services/repositories/src/main/java/au/com/shiftyjelly/pocketcasts/repositories/opml/OpmlImportTask.kt @@ -8,6 +8,7 @@ import android.widget.Toast import androidx.hilt.work.HiltWorker import androidx.work.Constraints import androidx.work.CoroutineWorker +import androidx.work.Data import androidx.work.ForegroundInfo import androidx.work.NetworkType import androidx.work.OneTimeWorkRequestBuilder @@ -35,6 +36,7 @@ import org.xml.sax.SAXException import org.xml.sax.helpers.DefaultHandler import java.io.InputStream import java.io.StringReader +import java.net.URL import java.util.Scanner import java.util.regex.Pattern import javax.xml.parsers.SAXParserFactory @@ -52,13 +54,23 @@ class OpmlImportTask @AssistedInject constructor( companion object { const val INPUT_URI = "INPUT_URI" + const val INPUT_URL = "INPUT_URL" fun run(uri: Uri, context: Context) { + val data = workDataOf(INPUT_URI to uri.toString()) + run(data, context) + } + + fun run(url: String, context: Context) { + val data = workDataOf(INPUT_URL to url) + run(data, context) + } + + private fun run(data: Data, context: Context) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() - val data = workDataOf(INPUT_URI to uri.toString()) val task = OneTimeWorkRequestBuilder() .setInputData(data) .setConstraints(constraints) @@ -132,6 +144,12 @@ class OpmlImportTask @AssistedInject constructor( override suspend fun doWork(): Result { try { + val url = inputData.getString(INPUT_URL) + if (!url.isNullOrBlank()) { + processUrl(URL(url)) + return Result.success() + } + val uri = Uri.parse(inputData.getString(INPUT_URI)) ?: return Result.failure() processFile(uri) return Result.success() @@ -141,6 +159,22 @@ class OpmlImportTask @AssistedInject constructor( } } + private suspend fun processUrl(url: URL) { + var urls = emptyList() + + try { + url.openStream()?.use { inputStream -> + urls = readOpmlUrlsSax(inputStream) + } + } catch (e: SAXException) { + url.openStream()?.use { inputStream -> + urls = readOpmlUrlsRegex(inputStream) + } + } + + processUrls(urls) + } + private suspend fun processFile(uri: Uri) { var urls = emptyList() @@ -155,6 +189,10 @@ class OpmlImportTask @AssistedInject constructor( } } + processUrls(urls) + } + + private suspend fun processUrls(urls: List) { val podcastCount = urls.size val initialDatabaseCount = podcastManager.countPodcasts()