From abdbf3dabdf4336e80f3ef3eaf583e6343eceeeb Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sat, 3 Aug 2024 00:02:45 +0100 Subject: [PATCH] 6.3.2 commit --- README.md | 1 + app/build.gradle | 4 +- .../podcini/preferences/OpmlTransporter.kt | 2 + .../ImportExportPreferencesFragment.kt | 32 +++++---- .../ac/mdiq/podcini/storage/model/Episode.kt | 3 +- .../podcini/storage/model/EpisodeMedia.kt | 2 +- .../CancelDownloadActionButton.kt | 11 ++- .../podcini/ui/adapter/SelectableAdapter.kt | 4 +- .../ui/fragment/EpisodeInfoFragment.kt | 7 +- .../podcini/ui/fragment/SearchFragment.kt | 4 +- .../ui/fragment/SubscriptionsFragment.kt | 69 +++++++++++++++---- .../drawable/baseline_import_export_24.xml | 7 ++ ...peeddial.xml => feed_action_speeddial.xml} | 10 +-- ...{nav_feed_context.xml => feed_context.xml} | 0 app/src/main/res/menu/nav_folder_context.xml | 7 -- changelog.md | 8 +++ .../android/en-US/changelogs/3020226.txt | 8 +++ 17 files changed, 124 insertions(+), 55 deletions(-) create mode 100644 app/src/main/res/drawable/baseline_import_export_24.xml rename app/src/main/res/menu/{nav_feed_action_speeddial.xml => feed_action_speeddial.xml} (85%) rename app/src/main/res/menu/{nav_feed_context.xml => feed_context.xml} (100%) delete mode 100644 app/src/main/res/menu/nav_folder_context.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/3020226.txt diff --git a/README.md b/README.md index 9deffb15..e9e8c0ff 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,7 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c * Play history/progress can be separately exported/imported as Json files * Downloaded media files can be exported/imported * Reconsile feature (accessed from Downloads view) is added to ensure downloaded media files are in sync with specs in DB +* Podcasts can be selectively exported from Subscriptions view * There is a setting to disable/enable auto backup of OPML files to Google * Upon re-install of Podcini, the OPML file previously backed up to Google is not imported automatically but based on user confirmation. diff --git a/app/build.gradle b/app/build.gradle index f1bb2e20..5df794c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { testApplicationId "ac.mdiq.podcini.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 3020225 - versionName "6.3.1" + versionCode 3020226 + versionName "6.3.2" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt index 78f570dd..7fba93f0 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt @@ -68,6 +68,8 @@ class OpmlTransporter { xs.startTag(null, OpmlSymbols.BODY) for (feed in feeds!!) { + if (feed == null) continue + Logd(TAG, "writeDocument ${feed?.title}") xs.startTag(null, OpmlSymbols.OUTLINE) xs.attribute(null, OpmlSymbols.TEXT, feed!!.title) xs.attribute(null, OpmlSymbols.TITLE, feed.title) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt index 8c376b99..c511fb82 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt @@ -23,6 +23,8 @@ import ac.mdiq.podcini.storage.utils.FileNameGenerator.generateFileName import ac.mdiq.podcini.storage.utils.FilesUtils.getDataFolder import ac.mdiq.podcini.ui.activity.OpmlImportActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity +import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment +import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion import ac.mdiq.podcini.util.Logd import android.app.Activity.RESULT_OK import android.app.ProgressDialog @@ -264,7 +266,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { } private fun exportDatabase() { - backupDatabaseLauncher.launch(dateStampFilename(DATABASE_EXPORT_FILENAME)) + backupDatabaseLauncher.launch(dateStampFilename("PodciniBackup-%s.realm")) } private fun importDatabase() { @@ -554,15 +556,16 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { } } - private enum class Export(val contentType: String, val outputNameTemplate: String, @field:StringRes val labelResId: Int) { - OPML(CONTENT_TYPE_OPML, DEFAULT_OPML_OUTPUT_NAME, R.string.opml_export_label), - HTML(CONTENT_TYPE_HTML, DEFAULT_HTML_OUTPUT_NAME, R.string.html_export_label), - FAVORITES(CONTENT_TYPE_HTML, DEFAULT_FAVORITES_OUTPUT_NAME, R.string.favorites_export_label), - PROGRESS(CONTENT_TYPE_PROGRESS, DEFAULT_PROGRESS_OUTPUT_NAME, R.string.progress_export_label), + enum class Export(val contentType: String, val outputNameTemplate: String, @field:StringRes val labelResId: Int) { + OPML(CONTENT_TYPE_OPML, "podcini-feeds-%s.opml", R.string.opml_export_label), + OPML_SELECTED(CONTENT_TYPE_OPML, "podcini-feeds-selected-%s.opml", R.string.opml_export_label), + HTML(CONTENT_TYPE_HTML, "podcini-feeds-%s.html", R.string.html_export_label), + FAVORITES(CONTENT_TYPE_HTML, "podcini-favorites-%s.html", R.string.favorites_export_label), + PROGRESS(CONTENT_TYPE_PROGRESS, "podcini-progress-%s.json", R.string.progress_export_label), } class DocumentFileExportWorker(private val exportWriter: ExportWriter, private val context: Context, private val outputFileUri: Uri) { - suspend fun exportFile(): DocumentFile { + suspend fun exportFile(feeds: List? = null): DocumentFile { return withContext(Dispatchers.IO) { val output = DocumentFile.fromSingleUri(context, outputFileUri) var outputStream: OutputStream? = null @@ -573,7 +576,9 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { outputStream = context.contentResolver.openOutputStream(uri, "wt") if (outputStream == null) throw IOException() writer = OutputStreamWriter(outputStream, Charset.forName("UTF-8")) - exportWriter.writeDocument(getFeedList(), writer, context) + val feeds_ = feeds ?: getFeedList() + Logd(TAG, "feeds_: ${feeds_.size}") + exportWriter.writeDocument(feeds_, writer, context) output } catch (e: IOException) { throw e @@ -591,7 +596,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { class ExportWorker private constructor(private val exportWriter: ExportWriter, private val output: File, private val context: Context) { constructor(exportWriter: ExportWriter, context: Context) : this(exportWriter, File(getDataFolder(EXPORT_DIR), DEFAULT_OUTPUT_NAME + "." + exportWriter.fileExtension()), context) - suspend fun exportFile(): File? { + suspend fun exportFile(feeds: List? = null): File? { return withContext(Dispatchers.IO) { if (output.exists()) { val success = output.delete() @@ -600,7 +605,9 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { var writer: OutputStreamWriter? = null try { writer = OutputStreamWriter(FileOutputStream(output), Charset.forName("UTF-8")) - exportWriter.writeDocument(getFeedList(), writer, context) + val feeds_ = feeds ?: getFeedList() + Logd(TAG, "feeds_: ${feeds_.size}") + exportWriter.writeDocument(feeds_, writer, context) output // return the output file } catch (e: IOException) { Log.e(TAG, "Error during file export", e) @@ -1135,13 +1142,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { companion object { private val TAG: String = ImportExportPreferencesFragment::class.simpleName ?: "Anonymous" - private const val DEFAULT_OPML_OUTPUT_NAME = "podcini-feeds-%s.opml" private const val CONTENT_TYPE_OPML = "text/x-opml" - private const val DEFAULT_HTML_OUTPUT_NAME = "podcini-feeds-%s.html" private const val CONTENT_TYPE_HTML = "text/html" - private const val DEFAULT_FAVORITES_OUTPUT_NAME = "podcini-favorites-%s.html" private const val CONTENT_TYPE_PROGRESS = "text/x-json" - private const val DEFAULT_PROGRESS_OUTPUT_NAME = "podcini-progress-%s.json" - private const val DATABASE_EXPORT_FILENAME = "PodciniBackup-%s.realm" } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt index 44b1cfb8..969a5abe 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Episode.kt @@ -1,6 +1,7 @@ package ac.mdiq.podcini.storage.model import ac.mdiq.podcini.storage.database.Feeds.getFeed +import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import io.realm.kotlin.ext.realmListOf import io.realm.kotlin.ext.realmSetOf import io.realm.kotlin.types.RealmList @@ -114,7 +115,7 @@ class Episode : RealmObject { val imageLocation: String? get() = when { imageUrl != null -> imageUrl - media != null && media!!.hasEmbeddedPicture() -> EpisodeMedia.FILENAME_PREFIX_EMBEDDED_COVER + media!!.getLocalMediaUrl() + media != null && unmanaged(media!!).hasEmbeddedPicture() -> EpisodeMedia.FILENAME_PREFIX_EMBEDDED_COVER + media!!.getLocalMediaUrl() feed != null -> { feed!!.imageUrl } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt index 73f8ce38..426238c8 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeMedia.kt @@ -313,7 +313,7 @@ class EpisodeMedia: EmbeddedRealmObject, Playable { override fun getImageLocation(): String? { return when { episode != null -> episode!!.imageLocation - hasEmbeddedPicture() -> FILENAME_PREFIX_EMBEDDED_COVER + getLocalMediaUrl() + unmanaged(this).hasEmbeddedPicture() -> FILENAME_PREFIX_EMBEDDED_COVER + getLocalMediaUrl() else -> null } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/CancelDownloadActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/CancelDownloadActionButton.kt index 4bdbf4bf..4c5c0ea2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/CancelDownloadActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/actionbutton/CancelDownloadActionButton.kt @@ -9,6 +9,10 @@ import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload import ac.mdiq.podcini.storage.database.Episodes.persistEpisode +import ac.mdiq.podcini.storage.database.RealmDB.upsert +import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk +import ac.mdiq.podcini.util.event.EventFlow +import ac.mdiq.podcini.util.event.FlowEvent class CancelDownloadActionButton(item: Episode) : EpisodeActionButton(item) { @StringRes @@ -25,8 +29,9 @@ class CancelDownloadActionButton(item: Episode) : EpisodeActionButton(item) { val media = item.media if (media != null) DownloadServiceInterface.get()?.cancel(context, media) if (isEnableAutodownload) { - item.disableAutoDownload() - persistEpisode(item) - } + val item_ = upsertBlk(item) { + it.disableAutoDownload() + } + EventFlow.postEvent(FlowEvent.EpisodeEvent.updated(item_)) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/SelectableAdapter.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/SelectableAdapter.kt index 8ce528c6..9dc22d17 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/SelectableAdapter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/adapter/SelectableAdapter.kt @@ -157,9 +157,7 @@ abstract class SelectableAdapter(private val activ totalCount = totalNumberOfItems if (shouldSelectLazyLoadedItems) selectedCount += (totalNumberOfItems - itemCount) } - actionMode!!.title = activity.resources - .getQuantityString(R.plurals.num_selected_label, selectedIds.size, - selectedCount, totalCount) + actionMode!!.title = activity.resources.getQuantityString(R.plurals.num_selected_label, selectedIds.size, selectedCount, totalCount) } fun setOnSelectModeListener(onSelectModeListener: OnSelectModeListener?) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt index 3d55c9e9..0146528a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt @@ -524,9 +524,10 @@ import kotlin.math.max } } // they didn't tell us the size, but we don't want to keep querying on it - if (size <= 0) media.setCheckedOnSizeButUnknown() - else media.size = size - upsert(episode) {} + upsert(episode) { + if (size <= 0) it.media?.setCheckedOnSizeButUnknown() + else it.media?.size = size + } size } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt index ed83deeb..8a41fd80 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SearchFragment.kt @@ -238,7 +238,7 @@ import java.lang.ref.WeakReference private fun onMenuItemClicked(fragment: Fragment, menuItemId: Int, selectedFeed: Feed, callback: Runnable): Boolean { val context = fragment.requireContext() when (menuItemId) { - R.id.rename_folder_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show() +// R.id.rename_folder_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show() R.id.edit_tags -> if (selectedFeed.preferences != null) TagSettingsDialog.newInstance(listOf(selectedFeed)) .show(fragment.childFragmentManager, TagSettingsDialog.TAG) R.id.rename_item -> CustomFeedNameDialog(fragment.activity as Activity, selectedFeed).show() @@ -492,7 +492,7 @@ import java.lang.ref.WeakReference override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?) { val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater if (longPressedItem == null) return - inflater.inflate(R.menu.nav_feed_context, contextMenu) + inflater.inflate(R.menu.feed_context, contextMenu) contextMenu.setHeaderTitle(longPressedItem!!.title) } fun setEndButton(@StringRes text: Int, action: Runnable?) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index 368f40c7..d70a2625 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -3,8 +3,10 @@ package ac.mdiq.podcini.ui.fragment import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.* import ac.mdiq.podcini.net.feed.FeedUpdateManager +import ac.mdiq.podcini.preferences.OpmlTransporter.OpmlWriter import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs +import ac.mdiq.podcini.preferences.fragments.ImportExportPreferencesFragment.* import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.Feeds.getTags import ac.mdiq.podcini.storage.database.RealmDB.realm @@ -28,15 +30,21 @@ import ac.mdiq.podcini.ui.utils.LiftOnScrollListener import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.FlowEvent +import android.app.Activity.RESULT_OK +import android.content.ActivityNotFoundException import android.content.Context import android.content.DialogInterface +import android.content.Intent import android.graphics.Rect import android.graphics.drawable.Drawable +import android.net.Uri import android.os.Bundle import android.util.Log import android.view.* import android.view.inputmethod.EditorInfo import android.widget.* +import androidx.activity.result.ActivityResult +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.OptIn import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.widget.Toolbar @@ -66,11 +74,8 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton import com.google.android.material.snackbar.Snackbar import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job +import kotlinx.coroutines.* import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.text.NumberFormat import java.text.SimpleDateFormat import java.util.* @@ -80,6 +85,14 @@ import java.util.* */ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener { + private val chooseOpmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> + if (result.resultCode != RESULT_OK || result.data == null) return@registerForActivityResult + val uri = result.data!!.data + multiSelectHandler?.exportOPML(uri) + } + + private var multiSelectHandler: FeedMultiSelectActionHandler? = null private var _binding: FragmentSubscriptionsBinding? = null private val binding get() = _binding!! @@ -181,7 +194,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec val speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root) speedDialView = speedDialBinding.fabSD speedDialView.overlayLayout = speedDialBinding.fabSDOverlay - speedDialView.inflate(R.menu.nav_feed_action_speeddial) + speedDialView.inflate(R.menu.feed_action_speeddial) speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { override fun onMainActionSelected(): Boolean { return false @@ -189,7 +202,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec override fun onToggleChanged(isOpen: Boolean) {} }) speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem -> - FeedMultiSelectActionHandler(activity as MainActivity, adapter.selectedItems.filterIsInstance()).handleAction(actionItem.id) + multiSelectHandler = FeedMultiSelectActionHandler(activity as MainActivity, adapter.selectedItems.filterIsInstance()) + multiSelectHandler?.handleAction(actionItem.id) true } loadSubscriptions() @@ -242,7 +256,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec private fun queryStringOfTags() : String { return when (tagFilterIndex) { 1 -> "" // All feeds - 0 -> " preferences.tags.@count == 0 OR (preferences.tags.@count == 0 AND ALL preferences.tags == '#root' ) " +// TODO: #root appears not used in RealmDB, is it a SQLite specialty + 0 -> " (preferences.tags.@count == 0 OR (preferences.tags.@count != 0 AND ALL preferences.tags == '#root' )) " else -> { // feeds with the chosen tag val tag = tags[tagFilterIndex] " ANY preferences.tags == '$tag' " @@ -334,8 +349,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec lifecycleScope.launch { try { withContext(Dispatchers.IO) { - filterAndSort() resetTags() + filterAndSort() } withContext(Dispatchers.Main) { // We have fewer items. This can result in items being selected that are no longer visible. @@ -502,7 +517,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec } @UnstableApi - private class FeedMultiSelectActionHandler(private val activity: MainActivity, private val selectedItems: List) { + private inner class FeedMultiSelectActionHandler(private val activity: MainActivity, private val selectedItems: List) { fun handleAction(id: Int) { when (id) { R.id.remove_feed -> RemoveFeedDialog.show(activity, selectedItems) @@ -510,11 +525,42 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec R.id.autodownload -> autoDownloadPrefHandler() R.id.autoDeleteDownload -> autoDeleteEpisodesPrefHandler() R.id.playback_speed -> playbackSpeedPrefHandler() + R.id.export_opml -> openExportPathPicker() R.id.associate_queue -> associatedQueuePrefHandler() R.id.edit_tags -> TagSettingsDialog.newInstance(selectedItems).show(activity.supportFragmentManager, TAG) else -> Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=$id") } } + private fun openExportPathPicker() { + val exportType = Export.OPML_SELECTED + val title = String.format(exportType.outputNameTemplate, SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())) + val intentPickAction = Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType(exportType.contentType) + .putExtra(Intent.EXTRA_TITLE, title) + try { + chooseOpmlExportPathLauncher.launch(intentPickAction) + return + } catch (e: ActivityNotFoundException) { + Log.e(TAG, "No activity found. Should never happen...") + } + // if on SDK lower than API 21 or the implicit intent failed, fallback to the legacy export process + exportOPML(null) + } + fun exportOPML(uri: Uri?) { + try { + runBlocking { + Logd(TAG, "selectedFeeds: ${selectedItems.size}") + if (uri == null) ExportWorker(OpmlWriter(), requireContext()).exportFile(selectedItems) + else { + val worker = DocumentFileExportWorker(OpmlWriter(), requireContext(), uri) + worker.exportFile(selectedItems) + } + } + } catch (e: Exception) { + Log.e(TAG, "exportOPML error: ${e.message}") + } + } private fun autoDownloadPrefHandler() { val preferenceSwitchDialog = PreferenceSwitchDialog(activity, activity.getString(R.string.auto_download_settings_label), activity.getString(R.string.auto_download_label)) preferenceSwitchDialog.setOnPreferenceChangedListener(@UnstableApi object: PreferenceSwitchDialog.OnPreferenceChangedListener { @@ -761,10 +807,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec get() { val items = ArrayList() for (i in 0 until itemCount) { - if (isSelected(i)) { - val feed: Feed = feedList[i] - items.add(feed) - } + if (isSelected(i)) items.add(feedList[i]) } return items } diff --git a/app/src/main/res/drawable/baseline_import_export_24.xml b/app/src/main/res/drawable/baseline_import_export_24.xml new file mode 100644 index 00000000..bc36d2c9 --- /dev/null +++ b/app/src/main/res/drawable/baseline_import_export_24.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/menu/nav_feed_action_speeddial.xml b/app/src/main/res/menu/feed_action_speeddial.xml similarity index 85% rename from app/src/main/res/menu/nav_feed_action_speeddial.xml rename to app/src/main/res/menu/feed_action_speeddial.xml index df761b5c..48fbbf64 100644 --- a/app/src/main/res/menu/nav_feed_action_speeddial.xml +++ b/app/src/main/res/menu/feed_action_speeddial.xml @@ -10,11 +10,6 @@ android:menuCategory="container" android:title="@string/keep_updated" android:icon="@drawable/ic_refresh"/> - - - - - + diff --git a/app/src/main/res/menu/nav_feed_context.xml b/app/src/main/res/menu/feed_context.xml similarity index 100% rename from app/src/main/res/menu/nav_feed_context.xml rename to app/src/main/res/menu/feed_context.xml diff --git a/app/src/main/res/menu/nav_folder_context.xml b/app/src/main/res/menu/nav_folder_context.xml deleted file mode 100644 index eb6515be..00000000 --- a/app/src/main/res/menu/nav_folder_context.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/changelog.md b/changelog.md index c72a3590..a67c2b15 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,11 @@ +# 6.3.2 + +* fixed crash of opening FeedEpisode view when "Use episode cover" is set +* fixed crash of opening EpisodeInfo view on episode with unknown media size +* fixed crash when cancelling download in a auto-download enabled feed +* fixed mis-behavior of "Untagged" filter in combination with other filters in Subscriptions view +* added "export selected feeds" in multi-select menu in Subscriptions view + # 6.3.1 * fixed crash when playing episode with missing media file diff --git a/fastlane/metadata/android/en-US/changelogs/3020226.txt b/fastlane/metadata/android/en-US/changelogs/3020226.txt new file mode 100644 index 00000000..386fa967 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020226.txt @@ -0,0 +1,8 @@ + +Version 6.3.2 brings several changes: + +* fixed crash of opening FeedEpisode view when "Use episode cover" is set +* fixed crash of opening EpisodeInfo view on episode with unknown media size +* fixed crash when cancelling download in a auto-download enabled feed +* fixed mis-behavior of "Untagged" filter in combination with other filters in Subscriptions view +* added "export selected feeds" in multi-select menu in Subscriptions view