From 2c23d9fc7e6517566f057e2e102d028b64a1054c Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Mon, 10 Jun 2024 15:56:20 +0100 Subject: [PATCH] 5.5.0 commit --- README.md | 1 + app/build.gradle | 27 ++-- .../service/DownloadServiceInterfaceImpl.kt | 15 --- .../net/sync/nextcloud/NextcloudLoginFlow.kt | 21 --- .../sync/nextcloud/NextcloudSyncService.kt | 3 +- .../playback/service/LocalMediaPlayer.kt | 5 - .../playback/service/PlaybackService.kt | 9 -- .../service/PlaybackServiceTaskManager.kt | 11 -- .../ImportExportPreferencesFragment.kt | 124 +++++++++++------- .../GpodderAuthenticationFragment.kt | 38 ------ .../podcini/receiver/MediaButtonReceiver.kt | 4 +- .../podcini/storage/PreferencesTransporter.kt | 6 +- .../asynctask/DocumentFileExportWorker.kt | 12 +- .../podcini/storage/asynctask/ExportWorker.kt | 6 +- .../export/favorites/FavoritesWriter.kt | 16 +-- .../export/progress/EpisodeProgressReader.kt | 70 ++++++++++ .../export/progress/EpisodesProgressWriter.kt | 70 ++++++++++ .../podcini/ui/activity/OpmlImportActivity.kt | 78 +---------- .../ui/activity/SelectSubscriptionActivity.kt | 19 --- .../podcini/ui/activity/SplashActivity.kt | 20 --- .../ac/mdiq/podcini/ui/dialog/ProxyDialog.kt | 54 -------- .../podcini/ui/dialog/RemoveFeedDialog.kt | 16 --- .../podcini/ui/dialog/TagSettingsDialog.kt | 13 -- .../podcini/ui/fragment/AddFeedFragment.kt | 13 -- .../ui/fragment/AudioPlayerFragment.kt | 29 ---- .../ui/fragment/BaseEpisodesListFragment.kt | 61 --------- .../podcini/ui/fragment/ChaptersFragment.kt | 14 -- .../podcini/ui/fragment/DiscoveryFragment.kt | 18 --- .../ui/fragment/DownloadLogFragment.kt | 12 -- .../podcini/ui/fragment/DownloadsFragment.kt | 34 ----- .../podcini/ui/fragment/FeedInfoFragment.kt | 14 -- .../ui/fragment/FeedItemlistFragment.kt | 56 -------- .../ui/fragment/FeedSettingsFragment.kt | 47 ------- .../ui/fragment/OnlineFeedViewFragment.kt | 37 ------ .../ui/fragment/PlayerDetailsFragment.kt | 42 ------ .../mdiq/podcini/ui/fragment/QueueFragment.kt | 14 -- .../ui/fragment/QuickFeedDiscoveryFragment.kt | 25 ---- .../podcini/ui/fragment/SearchFragment.kt | 20 --- .../ui/fragment/SubscriptionFragment.kt | 22 ---- .../ui/fragment/VideoEpisodeFragment.kt | 21 --- .../ui/statistics/StatisticsFragment.kt | 6 - .../downloads/DownloadStatisticsFragment.kt | 18 --- .../statistics/feed/FeedStatisticsFragment.kt | 18 --- .../SubscriptionStatisticsFragment.kt | 24 ---- .../years/YearsStatisticsFragment.kt | 11 -- app/src/main/res/layout/edit_text_dialog.xml | 2 +- app/src/main/res/values/strings.xml | 6 + .../res/xml/preferences_import_export.xml | 23 +++- changelog.md | 10 ++ .../android/en-US/changelogs/3020150.txt | 10 ++ 50 files changed, 287 insertions(+), 958 deletions(-) create mode 100644 app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodeProgressReader.kt create mode 100644 app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodesProgressWriter.kt create mode 100644 fastlane/metadata/android/en-US/changelogs/3020150.txt diff --git a/README.md b/README.md index f8230ca2..03ef33d9 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ The project aims to improve efficiency and provide more useful and user-friendly * Disabled `usesCleartextTraffic`, so that all content transmission is more private and secure * Settings/Preferences can now be exported and imported +* Play history/progress can be separately exported/imported as Json files For more details of the changes, see the [Changelog](changelog.md) diff --git a/app/build.gradle b/app/build.gradle index 4cd6813e..b021c42d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -159,8 +159,8 @@ android { // Version code schema (not used): // "1.2.3-beta4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 3020149 - versionName "5.4.2" + versionCode 3020150 + versionName "5.5.0" def commit = "" try { @@ -220,18 +220,18 @@ android { dependencies { implementation "androidx.core:core-ktx:1.12.0" - implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" +// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1' - implementation 'com.android.volley:volley:1.2.1' +// implementation 'com.android.volley:volley:1.2.1' - constraints { - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") { - because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") - } - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") { - because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") - } - } +// constraints { +// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version") { +// because("kotlin-stdlib-jdk7 is now a part of kotlin-stdlib") +// } +// implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version") { +// because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib") +// } +// } implementation "androidx.annotation:annotation:1.8.0" implementation "androidx.appcompat:appcompat:1.6.1" @@ -265,9 +265,6 @@ dependencies { implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" implementation 'com.squareup.okio:okio:3.9.0' -// implementation "org.greenrobot:eventbus:3.3.1" -// kapt "org.greenrobot:eventbus-annotation-processor:3.3.1" - implementation "io.reactivex.rxjava2:rxandroid:2.1.1" implementation "io.reactivex.rxjava2:rxjava:2.2.21" diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt b/app/src/main/java/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt index 6ebf8fd9..1ab2ba43 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt @@ -40,21 +40,6 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() { DBWriter.deleteFeedMediaOfItem(context, media.id) // Remove partially downloaded file val tag = WORK_TAG_EPISODE_URL + media.download_url val future: Future> = WorkManager.getInstance(context).getWorkInfosByTag(tag) -// Observable.fromFuture(future) -// .subscribeOn(Schedulers.io()) -// .observeOn(Schedulers.io()) -// .subscribe( -// { workInfos: List -> -// for (info in workInfos) { -// if (info.tags.contains(WORK_DATA_WAS_QUEUED)) { -// if (media.item != null) DBWriter.removeQueueItem(context, false, media.item!!) -// } -// } -// WorkManager.getInstance(context).cancelAllWorkByTag(tag) -// }, { exception: Throwable -> -// WorkManager.getInstance(context).cancelAllWorkByTag(tag) -// exception.printStackTrace() -// }) CoroutineScope(Dispatchers.IO).launch { try { diff --git a/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudLoginFlow.kt b/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudLoginFlow.kt index 5e0b31fa..e36aeebd 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudLoginFlow.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudLoginFlow.kt @@ -46,27 +46,6 @@ class NextcloudLoginFlow(private val httpClient: OkHttpClient, private val rawHo poll() return } -// startDisposable = Observable.fromCallable { -// val url = URI(hostname.scheme, null, hostname.host, hostname.port, hostname.subfolder + "/index.php/login/v2", null, null).toURL() -// val result = doRequest(url, "") -// val loginUrl = result.getString("login") -// this.token = result.getJSONObject("poll").getString("token") -// this.endpoint = result.getJSONObject("poll").getString("endpoint") -// loginUrl -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: String? -> -// val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(result)) -// context.startActivity(browserIntent) -// poll() -// }, { error: Throwable -> -// Log.e(TAG, Log.getStackTraceString(error)) -// this.token = null -// this.endpoint = null -// callback.onNextcloudAuthError(error.localizedMessage) -// }) val coroutineScope = CoroutineScope(Dispatchers.Main) coroutineScope.launch { diff --git a/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudSyncService.kt b/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudSyncService.kt index de6bff04..babb3b34 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudSyncService.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/sync/nextcloud/NextcloudSyncService.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.net.sync.model.* import okhttp3.* import okhttp3.Credentials.basic import okhttp3.MediaType.Companion.toMediaType +import org.apache.commons.lang3.StringUtils import org.json.JSONArray import org.json.JSONException import org.json.JSONObject @@ -123,7 +124,7 @@ class NextcloudSyncService(private val httpClient: OkHttpClient, baseHosturl: St val builder = HttpUrl.Builder() if (hostname.scheme != null) builder.scheme(hostname.scheme!!) if (hostname.host != null) builder.host(hostname.host!!) - return builder.port(hostname.port).addPathSegments(hostname.subfolder + path) + return builder.port(hostname.port).addPathSegments(StringUtils.stripStart(hostname.subfolder + path, "/")) } override fun logout() {} diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt index ae9ce165..59879dd6 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/service/LocalMediaPlayer.kt @@ -682,11 +682,6 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP createStaticPlayer(context) } playbackParameters = exoPlayer!!.playbackParameters -// bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe { -// bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage) -// } val scope = CoroutineScope(Dispatchers.Main) scope.launch { while (true) { diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt index 603fc88d..dc3b5408 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -627,15 +627,6 @@ class PlaybackService : MediaSessionService() { } private fun startPlayingFromPreferences() { -// Observable.fromCallable { createInstanceFromPreferences(applicationContext) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { playable: Playable? -> startPlaying(playable, false) }, -// { error: Throwable -> -// Logd(TAG, "Playable was not loaded from preferences. Stopping service.") -// error.printStackTrace() -// }) scope.launch { try { val playable = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceTaskManager.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceTaskManager.kt index 4fcd1a46..17b12af5 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceTaskManager.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/service/PlaybackServiceTaskManager.kt @@ -196,17 +196,6 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb // chapterLoaderFuture = null if (!media.chaptersLoaded()) { -// chapterLoaderFuture = Completable.create { emitter: CompletableEmitter -> -// ChapterUtils.loadChapters(media, context, false) -// emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ callback.onChapterLoaded(media) }, -// { throwable: Throwable? -> -// Logd(TAG, "Error loading chapters: " + Log.getStackTraceString(throwable)) -// }) - val scope = CoroutineScope(Dispatchers.Main) scope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt index c132f94c..5ee094bf 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/ImportExportPreferencesFragment.kt @@ -2,19 +2,18 @@ package ac.mdiq.podcini.preferences.fragments import ac.mdiq.podcini.PodciniApp.Companion.forceRestart import ac.mdiq.podcini.R -import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DatabaseTransporter import ac.mdiq.podcini.storage.PreferencesTransporter import ac.mdiq.podcini.storage.asynctask.DocumentFileExportWorker import ac.mdiq.podcini.storage.asynctask.ExportWorker import ac.mdiq.podcini.storage.export.ExportWriter +import ac.mdiq.podcini.storage.export.progress.EpisodeProgressReader +import ac.mdiq.podcini.storage.export.progress.EpisodesProgressWriter import ac.mdiq.podcini.storage.export.favorites.FavoritesWriter import ac.mdiq.podcini.storage.export.html.HtmlWriter import ac.mdiq.podcini.storage.export.opml.OpmlWriter import ac.mdiq.podcini.ui.activity.OpmlImportActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity -import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog -import ac.mdiq.podcini.util.Logd import android.app.Activity.RESULT_OK import android.app.ProgressDialog import android.content.ActivityNotFoundException @@ -37,35 +36,36 @@ import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import io.reactivex.Completable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.io.File +import java.io.* import java.text.SimpleDateFormat import java.util.* class ImportExportPreferencesFragment : PreferenceFragmentCompat() { - private val chooseOpmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - this.chooseOpmlExportPathResult(result) } - private val chooseHtmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - this.chooseHtmlExportPathResult(result) } - private val chooseFavoritesExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - this.chooseFavoritesExportPathResult(result) } - private val restoreDatabaseLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - this.restoreDatabaseResult(result) } + private val chooseOpmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.chooseOpmlExportPathResult(result) } + private val chooseHtmlExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.chooseHtmlExportPathResult(result) } + private val chooseFavoritesExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.chooseFavoritesExportPathResult(result) } + private val chooseProgressExportPathLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.chooseProgressExportPathResult(result) } + private val restoreProgressLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.restoreProgressResult(result) } + private val restoreDatabaseLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.restoreDatabaseResult(result) } private val backupDatabaseLauncher = registerForActivityResult(BackupDatabase()) { uri: Uri? -> this.backupDatabaseResult(uri) } - private val chooseOpmlImportPathLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri: Uri? -> - this.chooseOpmlImportPathResult(uri) } + private val chooseOpmlImportPathLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { + uri: Uri? -> this.chooseOpmlImportPathResult(uri) } - private val restorePreferencesLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> - this.restorePreferencesResult(result) - } + private val restorePreferencesLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + result: ActivityResult -> this.restorePreferencesResult(result) } private val backupPreferencesLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == RESULT_OK) { val data: Uri? = it.data?.data @@ -107,6 +107,14 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { openExportPathPicker(Export.HTML, chooseHtmlExportPathLauncher, HtmlWriter()) true } + findPreference(PREF_PROGRESS_EXPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + openExportPathPicker(Export.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter()) + true + } + findPreference(PREF_PROGRESS_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + importEpisodeProgress() + true + } findPreference(PREF_OPML_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { try { chooseOpmlImportPathLauncher.launch("*/*") @@ -123,11 +131,11 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { exportDatabase() true } - findPreference(PREF_PREFERENCES_IMPORT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findPreference(PREF_PREFERENCES_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { importPreferences() true } - findPreference(PREF_PREFERENCES_EXPORT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findPreference(PREF_PREFERENCES_EXPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { exportPreferences() true } @@ -231,6 +239,29 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { alert.show() } + private fun importEpisodeProgress() { + // setup the alert builder + val builder = MaterialAlertDialogBuilder(requireActivity()) + builder.setTitle(R.string.progress_import_label) + builder.setMessage(R.string.progress_import_warning) + + // add a button + builder.setNegativeButton(R.string.no, null) + builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.setType("*/*") + restoreProgressLauncher.launch(intent) + } + // create and show the alert dialog + builder.show() + } + + private fun chooseProgressExportPathResult(result: ActivityResult) { + if (result.resultCode != RESULT_OK || result.data == null) return + val uri = result.data!!.data + exportWithWriter(EpisodesProgressWriter(), uri, Export.PROGRESS) + } + private fun chooseOpmlExportPathResult(result: ActivityResult) { if (result.resultCode != RESULT_OK || result.data == null) return val uri = result.data!!.data @@ -249,18 +280,32 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { exportWithWriter(FavoritesWriter(), uri, Export.FAVORITES) } + private fun restoreProgressResult(result: ActivityResult) { + if (result.resultCode != RESULT_OK || result.data?.data == null) return + val uri = result.data!!.data!! + progressDialog!!.show() + lifecycleScope.launch { + try { + withContext(Dispatchers.IO) { + val inputStream: InputStream? = requireContext().contentResolver.openInputStream(uri) + val reader = BufferedReader(InputStreamReader(inputStream)) + EpisodeProgressReader.readDocument(reader) + reader.close() + } + withContext(Dispatchers.Main) { + showDatabaseImportSuccessDialog() + progressDialog!!.dismiss() + } + } catch (e: Throwable) { + showExportErrorDialog(e) + } + } + } + private fun restoreDatabaseResult(result: ActivityResult) { if (result.resultCode != RESULT_OK || result.data == null) return val uri = result.data!!.data progressDialog!!.show() -// disposable = Completable.fromAction { DatabaseTransporter.importBackup(uri, requireContext()) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -// showDatabaseImportSuccessDialog() -// progressDialog!!.dismiss() -// }, { error: Throwable -> this.showExportErrorDialog(error) }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -280,14 +325,6 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { if (result.resultCode != RESULT_OK || result.data?.data == null) return val uri = result.data!!.data!! progressDialog!!.show() -// disposable = Completable.fromAction { PreferencesTransporter.importBackup(uri, requireContext()) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -// showDatabaseImportSuccessDialog() -// progressDialog!!.dismiss() -// }, { error: Throwable -> this.showExportErrorDialog(error) }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -306,14 +343,6 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { private fun backupDatabaseResult(uri: Uri?) { if (uri == null) return progressDialog!!.show() -// disposable = Completable.fromAction { DatabaseTransporter.exportToDocument(uri, requireContext()) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -// showExportSuccessSnackbar(uri, "application/x-sqlite3") -// progressDialog!!.dismiss() -// }, { error: Throwable -> this.showExportErrorDialog(error) }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -370,12 +399,15 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { 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), } companion object { private const val TAG = "ImportExPrefFragment" private const val PREF_OPML_EXPORT = "prefOpmlExport" private const val PREF_OPML_IMPORT = "prefOpmlImport" + private const val PREF_PROGRESS_EXPORT = "prefProgressExport" + private const val PREF_PROGRESS_IMPORT = "prefProgressImport" private const val PREF_HTML_EXPORT = "prefHtmlExport" private const val PREF_PREFERENCES_IMPORT = "prefPrefImport" private const val PREF_PREFERENCES_EXPORT = "prefPrefExport" @@ -387,6 +419,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() { 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.db" } } diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/GpodderAuthenticationFragment.kt b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/GpodderAuthenticationFragment.kt index 3fc8f7f2..e812c9aa 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/GpodderAuthenticationFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/GpodderAuthenticationFragment.kt @@ -110,26 +110,6 @@ class GpodderAuthenticationFragment : DialogFragment() { val inputManager = requireContext().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager inputManager.hideSoftInputFromWindow(login.windowToken, InputMethodManager.HIDE_NOT_ALWAYS) -// Completable.fromAction { -// service?.setCredentials(usernameStr, passwordStr) -// service?.login() -// if (service != null) devices = service!!.devices -// this@GpodderAuthenticationFragment.username = usernameStr -// this@GpodderAuthenticationFragment.password = passwordStr -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -// login.isEnabled = true -// progressBar.visibility = View.GONE -// advance() -// }, { error: Throwable -> -// login.isEnabled = true -// progressBar.visibility = View.GONE -// txtvError.text = error.cause!!.message -// txtvError.visibility = View.VISIBLE -// }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -189,24 +169,6 @@ class GpodderAuthenticationFragment : DialogFragment() { txtvError.visibility = View.GONE deviceName.isEnabled = false -// Observable.fromCallable { -// val deviceId = generateDeviceId(deviceNameStr) -// service!!.configureDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE) -// GpodnetDevice(deviceId, deviceNameStr, GpodnetDevice.DeviceType.MOBILE.toString(), 0) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ device: GpodnetDevice? -> -// progBarCreateDevice.visibility = View.GONE -// selectedDevice = device -// advance() -// }, { error: Throwable -> -// deviceName.isEnabled = true -// progBarCreateDevice.visibility = View.GONE -// txtvError.text = error.message -// txtvError.visibility = View.VISIBLE -// }) - lifecycleScope.launch { try { val device = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt b/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt index a894d96d..b538ee2b 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt @@ -1,16 +1,14 @@ package ac.mdiq.podcini.receiver import ac.mdiq.podcini.util.Logd +import ac.mdiq.podcini.util.config.ClientConfigurator import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.os.Build -import android.util.Log import android.view.KeyEvent import androidx.core.content.ContextCompat import androidx.media3.common.util.UnstableApi -import ac.mdiq.podcini.util.config.ClientConfigurator /** * Receives media button events. diff --git a/app/src/main/java/ac/mdiq/podcini/storage/PreferencesTransporter.kt b/app/src/main/java/ac/mdiq/podcini/storage/PreferencesTransporter.kt index 205b066f..419a03cf 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/PreferencesTransporter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/PreferencesTransporter.kt @@ -77,9 +77,8 @@ object PreferencesTransporter { // val prefName = file.name.substring(0, file.name.lastIndexOf('.')) file.delete() } - } else { - Log.e("Error", "shared_prefs directory not found") - } + } else Log.e("Error", "shared_prefs directory not found") + val files = exportedDir.listFiles() for (file in files) { if (file?.isFile == true && file.name?.endsWith(".xml") == true) { @@ -91,6 +90,5 @@ object PreferencesTransporter { Log.e(TAG, Log.getStackTraceString(e)) throw e } finally { } - } } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/asynctask/DocumentFileExportWorker.kt b/app/src/main/java/ac/mdiq/podcini/storage/asynctask/DocumentFileExportWorker.kt index b5fa5184..bcff4e06 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/asynctask/DocumentFileExportWorker.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/asynctask/DocumentFileExportWorker.kt @@ -34,18 +34,10 @@ class DocumentFileExportWorker(private val exportWriter: ExportWriter, private v subscriber.onError(e) } finally { if (writer != null) { - try { - writer.close() - } catch (e: IOException) { - subscriber.onError(e) - } + try { writer.close() } catch (e: IOException) { subscriber.onError(e) } } if (outputStream != null) { - try { - outputStream.close() - } catch (e: IOException) { - subscriber.onError(e) - } + try { outputStream.close() } catch (e: IOException) { subscriber.onError(e) } } subscriber.onComplete() } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/asynctask/ExportWorker.kt b/app/src/main/java/ac/mdiq/podcini/storage/asynctask/ExportWorker.kt index 4e9bac93..9aac3b5d 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/asynctask/ExportWorker.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/asynctask/ExportWorker.kt @@ -36,11 +36,7 @@ class ExportWorker private constructor(private val exportWriter: ExportWriter, p subscriber.onError(e) } finally { if (writer != null) { - try { - writer.close() - } catch (e: IOException) { - subscriber.onError(e) - } + try { writer.close() } catch (e: IOException) { subscriber.onError(e) } } subscriber.onComplete() } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/export/favorites/FavoritesWriter.kt b/app/src/main/java/ac/mdiq/podcini/storage/export/favorites/FavoritesWriter.kt index 8ccf2797..0df33286 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/export/favorites/FavoritesWriter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/export/favorites/FavoritesWriter.kt @@ -16,6 +16,7 @@ import java.util.* /** Writes saved favorites to file. */ class FavoritesWriter : ExportWriter { + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) override fun writeDocument(feeds: List?, writer: Writer?, context: Context) { Logd(TAG, "Starting to write document") @@ -27,30 +28,23 @@ class FavoritesWriter : ExportWriter { val favTemplateStream = context.assets.open(FAVORITE_TEMPLATE) val favTemplate = IOUtils.toString(favTemplateStream, UTF_8) - val feedTemplateStream = context.assets.open(FEED_TEMPLATE) val feedTemplate = IOUtils.toString(feedTemplateStream, UTF_8) - - val allFavorites = getEpisodes(0, Int.MAX_VALUE, - FeedItemFilter(FeedItemFilter.IS_FAVORITE), SortOrder.DATE_NEW_OLD) + val allFavorites = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.IS_FAVORITE), SortOrder.DATE_NEW_OLD) val favoriteByFeed = getFeedMap(allFavorites) - writer!!.append(templateParts[0]) for (feedId in favoriteByFeed.keys) { val favorites: List = favoriteByFeed[feedId]!! writer.append("
  • \n") writeFeed(writer, favorites[0].feed, feedTemplate) - writer.append("
      \n") for (item in favorites) { writeFavoriteItem(writer, item, favTemplate) } writer.append("
  • \n") } - writer.append(templateParts[1]) - Logd(TAG, "Finished writing document") } @@ -62,18 +56,14 @@ class FavoritesWriter : ExportWriter { */ private fun getFeedMap(favoritesList: List): Map> { val feedMap: MutableMap> = TreeMap() - for (item in favoritesList) { var feedEpisodes = feedMap[item.feedId] - if (feedEpisodes == null) { feedEpisodes = ArrayList() feedMap[item.feedId] = feedEpisodes } - feedEpisodes.add(item) } - return feedMap } @@ -93,10 +83,8 @@ class FavoritesWriter : ExportWriter { var favItem = favoriteTemplate.replace("{FAV_TITLE}", item.title!!.trim { it <= ' ' }) favItem = if (item.link != null) favItem.replace("{FAV_WEBSITE}", item.link!!) else favItem.replace("{FAV_WEBSITE}", "") - favItem = if (item.media != null && item.media!!.download_url != null) favItem.replace("{FAV_MEDIA}", item.media!!.download_url!!) else favItem.replace("{FAV_MEDIA}", "") - writer!!.append(favItem) } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodeProgressReader.kt b/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodeProgressReader.kt new file mode 100644 index 00000000..b32ed545 --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodeProgressReader.kt @@ -0,0 +1,70 @@ +package ac.mdiq.podcini.storage.export.progress + +import ac.mdiq.podcini.net.sync.SyncService +import ac.mdiq.podcini.net.sync.SyncService.Companion.isValidGuid +import ac.mdiq.podcini.net.sync.model.EpisodeAction +import ac.mdiq.podcini.net.sync.model.EpisodeAction.Companion.readFromJsonObject +import ac.mdiq.podcini.storage.DBReader.getFeedItemByGuidOrEpisodeUrl +import ac.mdiq.podcini.storage.DBReader.loadAdditionalFeedItemListData +import ac.mdiq.podcini.storage.DBWriter.persistItemList +import ac.mdiq.podcini.storage.model.feed.FeedItem +import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded +import ac.mdiq.podcini.util.Logd +import android.util.Log +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi +import org.json.JSONArray +import java.io.Reader + +/** Reads OPML documents. */ +object EpisodeProgressReader { + private const val TAG = "EpisodeProgressReader" + + @OptIn(UnstableApi::class) + fun readDocument(reader: Reader) { + val jsonString = reader.readText() + val jsonArray = JSONArray(jsonString) + val remoteActions = mutableListOf() + for (i in 0 until jsonArray.length()) { + val jsonAction = jsonArray.getJSONObject(i) + Logd(TAG, "Loaded EpisodeActions message: $i $jsonAction") + val action = readFromJsonObject(jsonAction) + if (action != null) remoteActions.add(action) + } + if (remoteActions.isEmpty()) return + + val updatedItems: MutableList = ArrayList() + for (action in remoteActions) { + val result = processEpisodeAction(action) ?: continue + updatedItems.add(result.second) + } + loadAdditionalFeedItemListData(updatedItems) + persistItemList(updatedItems) + + Logd(TAG, "Parsing finished.") + return + } + + private fun processEpisodeAction(action: EpisodeAction): Pair? { + val guid = if (isValidGuid(action.guid)) action.guid else null + val feedItem = getFeedItemByGuidOrEpisodeUrl(guid, action.episode?:"") + if (feedItem == null) { + Log.i(SyncService.TAG, "Unknown feed item: $action") + return null + } + if (feedItem.media == null) { + Log.i(SyncService.TAG, "Feed item has no media: $action") + return null + } + var idRemove = 0L + feedItem.media!!.setPosition(action.position * 1000) + if (hasAlmostEnded(feedItem.media!!)) { + Logd(SyncService.TAG, "Marking as played: $action") + feedItem.setPlayed(true) + feedItem.media!!.setPosition(0) + idRemove = feedItem.id + } else Logd(SyncService.TAG, "Setting position: $action") + + return Pair(idRemove, feedItem) + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodesProgressWriter.kt b/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodesProgressWriter.kt new file mode 100644 index 00000000..fa5bcfc2 --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/storage/export/progress/EpisodesProgressWriter.kt @@ -0,0 +1,70 @@ +package ac.mdiq.podcini.storage.export.progress + +import ac.mdiq.podcini.net.sync.model.EpisodeAction +import ac.mdiq.podcini.net.sync.model.SyncServiceException +import ac.mdiq.podcini.storage.DBReader.getEpisodes +import ac.mdiq.podcini.storage.export.ExportWriter +import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.storage.model.feed.FeedItem +import ac.mdiq.podcini.storage.model.feed.FeedItemFilter +import ac.mdiq.podcini.storage.model.feed.SortOrder +import ac.mdiq.podcini.util.Logd +import android.content.Context +import org.apache.commons.lang3.StringUtils +import org.json.JSONArray +import java.io.IOException +import java.io.Writer +import java.util.* + +/** Writes saved favorites to file. */ +class EpisodesProgressWriter : ExportWriter { + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + override fun writeDocument(feeds: List?, writer: Writer?, context: Context) { + Logd(TAG, "Starting to write document") + val queuedEpisodeActions: MutableList = mutableListOf() + val pausedItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PAUSED), SortOrder.DATE_NEW_OLD) + val readItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PLAYED), SortOrder.DATE_NEW_OLD) + val comItems = mutableSetOf() + comItems.addAll(pausedItems) + comItems.addAll(readItems) + Logd(TAG, "Save state for all " + comItems.size + " played episodes") + for (item in comItems) { + val media = item.media ?: continue + val played = EpisodeAction.Builder(item, EpisodeAction.PLAY) + .timestamp(Date(media.getLastPlayedTime())) + .started(media.getPosition() / 1000) + .position(media.getPosition() / 1000) + .total(media.getDuration() / 1000) + .build() + queuedEpisodeActions.add(played) + } + + if (queuedEpisodeActions.isNotEmpty()) { + try { + Logd(TAG, "Saving ${queuedEpisodeActions.size} actions: ${StringUtils.join(queuedEpisodeActions, ", ")}") + val list = JSONArray() + for (episodeAction in queuedEpisodeActions) { + val obj = episodeAction.writeToJsonObject() + if (obj != null) { + Logd(TAG, "saving EpisodeAction: $obj") + list.put(obj) + } + } + writer?.write(list.toString()) + } catch (e: Exception) { + e.printStackTrace() + throw SyncServiceException(e) + } + } + Logd(TAG, "Finished writing document") + } + + override fun fileExtension(): String { + return "json" + } + + companion object { + private const val TAG = "EpisodesProgressWriter" + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/OpmlImportActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/OpmlImportActivity.kt index dd6d5bef..22620bde 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/OpmlImportActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/OpmlImportActivity.kt @@ -84,35 +84,6 @@ class OpmlImportActivity : AppCompatActivity() { } binding.butConfirm.setOnClickListener { binding.progressBar.visibility = View.VISIBLE -// Completable.fromAction { -// val checked = binding.feedlist.checkedItemPositions -// for (i in 0 until checked.size()) { -// if (!checked.valueAt(i)) continue -// -// if (!readElements.isNullOrEmpty()) { -// val element = readElements!![checked.keyAt(i)] -// val feed = Feed(element.xmlUrl, null, if (element.text != null) element.text else "Unknown podcast") -// feed.items = mutableListOf() -// DBTasks.updateFeed(this, feed, false) -// } -// } -// runOnce(this) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { -// binding.progressBar.visibility = View.GONE -// val intent = Intent(this@OpmlImportActivity, MainActivity::class.java) -// intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK) -// startActivity(intent) -// finish() -// }, { e: Throwable -> -// e.printStackTrace() -// binding.progressBar.visibility = View.GONE -// Toast.makeText(this, e.message, Toast.LENGTH_LONG).show() -// }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -151,7 +122,7 @@ class OpmlImportActivity : AppCompatActivity() { importUri(uri) } - fun importUri(uri: Uri?) { + private fun importUri(uri: Uri?) { if (uri == null) { MaterialAlertDialogBuilder(this) .setMessage(R.string.opml_import_error_no_file) @@ -230,53 +201,6 @@ class OpmlImportActivity : AppCompatActivity() { private fun startImport() { binding.progressBar.visibility = View.VISIBLE -// Observable.fromCallable { -// val opmlFileStream = contentResolver.openInputStream(uri!!) -// val bomInputStream = BOMInputStream(opmlFileStream) -// val bom = bomInputStream.bom -// val charsetName = if (bom == null) "UTF-8" else bom.charsetName -// val reader: Reader = InputStreamReader(bomInputStream, charsetName) -// val opmlReader = OpmlReader() -// val result = opmlReader.readDocument(reader) -// reader.close() -// result -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: ArrayList? -> -// binding.progressBar.visibility = View.GONE -// Logd(TAG, "Parsing was successful") -// readElements = result -// listAdapter = ArrayAdapter(this@OpmlImportActivity, android.R.layout.simple_list_item_multiple_choice, titleList) -// binding.feedlist.adapter = listAdapter -// }, { e: Throwable -> -// Logd(TAG, Log.getStackTraceString(e)) -// val message = if (e.message == null) "" else e.message!! -// if (message.lowercase().contains("permission")) { -// val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) -// if (permission != PackageManager.PERMISSION_GRANTED) { -// requestPermission() -// return@subscribe -// } -// } -// binding.progressBar.visibility = View.GONE -// val alert = MaterialAlertDialogBuilder(this) -// alert.setTitle(R.string.error_label) -// val userReadable = getString(R.string.opml_reader_error) -// val details = e.message -// val total = """ -// $userReadable -// -// $details -// """.trimIndent() -// val errorMessage = SpannableString(total) -// errorMessage.setSpan(ForegroundColorSpan(-0x77777778), userReadable.length, total.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) -// alert.setMessage(errorMessage) -// alert.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> finish() } -// alert.show() -// }) - lifecycleScope.launch(Dispatchers.IO) { try { val opmlFileStream = contentResolver.openInputStream(uri!!) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/SelectSubscriptionActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/SelectSubscriptionActivity.kt index 8015be75..35f38a7b 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/SelectSubscriptionActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/SelectSubscriptionActivity.kt @@ -120,25 +120,6 @@ class SelectSubscriptionActivity : AppCompatActivity() { } private fun loadSubscriptions() { -// disposable?.dispose() - -// disposable = Observable.fromCallable { -// val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) -// getFeedItems(data.items, ArrayList()) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: List -> -// listItems = result -// val titles = ArrayList() -// for (feed in result) { -// if (feed.title != null) titles.add(feed.title!!) -// } -// val adapter: ArrayAdapter = ArrayAdapter(this, R.layout.simple_list_item_multiple_choice_on_start, titles) -// binding.list.adapter = adapter -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val result = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/SplashActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/SplashActivity.kt index fc333a12..67fb580f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/SplashActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/SplashActivity.kt @@ -24,26 +24,6 @@ class SplashActivity : Activity() { val content = findViewById(android.R.id.content) content.viewTreeObserver.addOnPreDrawListener { false } // Keep splash screen active -// Completable.create { subscriber: CompletableEmitter -> -// // Trigger schema updates -// PodDBAdapter.getInstance().open() -// PodDBAdapter.getInstance().close() -// subscriber.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -// val intent = Intent(this@SplashActivity, MainActivity::class.java) -// startActivity(intent) -// overridePendingTransition(0, 0) -// finish() -// }, { error: Throwable -> -// error.printStackTrace() -// CrashReportWriter.write(error) -// Toast.makeText(this, error.localizedMessage, Toast.LENGTH_LONG).show() -// finish() -// }) - val scope = CoroutineScope(Dispatchers.IO) scope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ProxyDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ProxyDialog.kt index fb0c757c..0a179670 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ProxyDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ProxyDialog.kt @@ -230,60 +230,6 @@ class ProxyDialog(private val context: Context) { txtvMessage.text = "{fa-circle-o-notch spin} $checking" txtvMessage.visibility = View.VISIBLE -// disposable = Completable.create { emitter: CompletableEmitter -> -// val type = spType.selectedItem as String -// val host = etHost.text.toString() -// val port = etPort.text.toString() -// val username = etUsername.text.toString() -// val password = etPassword.text.toString() -// var portValue = 8080 -// if (port.isNotEmpty()) portValue = port.toInt() -// -// val address: SocketAddress = InetSocketAddress.createUnresolved(host, portValue) -// val proxyType = Proxy.Type.valueOf(type.uppercase()) -// val builder: OkHttpClient.Builder = newBuilder() -// .connectTimeout(10, TimeUnit.SECONDS) -// .proxy(Proxy(proxyType, address)) -// if (username.isNotEmpty()) { -// builder.proxyAuthenticator { _: Route?, response: Response -> -// val credentials = basic(username, password) -// response.request.newBuilder() -// .header("Proxy-Authorization", credentials) -// .build() -// } -// } -// val client: OkHttpClient = builder.build() -// val request: Request = Builder().url("https://www.example.com").head().build() -// try { -// client.newCall(request).execute().use { response -> -// if (response.isSuccessful) { -// emitter.onComplete() -// } else { -// emitter.onError(IOException(response.message)) -// } -// } -// } catch (e: IOException) { -// emitter.onError(e) -// } -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { -// txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_green)) -// val message = String.format("%s %s", "{fa-check}", context.getString(R.string.proxy_test_successful)) -// txtvMessage.text = message -// setTestRequired(false) -// }, -// { error: Throwable -> -// error.printStackTrace() -// txtvMessage.setTextColor(getColorFromAttr(context, R.attr.icon_red)) -// val message = String.format("%s %s: %s", "{fa-close}", context.getString(R.string.proxy_test_failed), error.message) -// txtvMessage.text = message -// setTestRequired(true) -// } -// ) - val coroutineScope = CoroutineScope(Dispatchers.Main) coroutineScope.launch(Dispatchers.IO) { try { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt index 6a054211..7fd7f71e 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/RemoveFeedDialog.kt @@ -40,22 +40,6 @@ object RemoveFeedDialog { progressDialog.setCancelable(false) progressDialog.show() -// Completable.fromAction { -// for (feed in feeds) { -// DBWriter.deleteFeed(context, feed.id).get() -// } -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { -// Logd(TAG, "Feed(s) deleted") -// progressDialog.dismiss() -// }, { error: Throwable? -> -// Log.e(TAG, Log.getStackTraceString(error)) -// progressDialog.dismiss() -// }) - val scope = CoroutineScope(Dispatchers.Main) scope.launch { try { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/TagSettingsDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/TagSettingsDialog.kt index 4e148eae..f29ca882 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/TagSettingsDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/TagSettingsDialog.kt @@ -88,19 +88,6 @@ class TagSettingsDialog : DialogFragment() { } private fun loadTags() { -// Observable.fromCallable { -// DBReader.getTags() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: List -> -// val acAdapter = ArrayAdapter(requireContext(), R.layout.single_tag_text_view, result) -// binding.newTagEditText.setAdapter(acAdapter) -// }, { error: Throwable? -> -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - val scope = CoroutineScope(Dispatchers.Main) scope.launch { try { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt index 0433b877..681ad147 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt @@ -164,19 +164,6 @@ class AddFeedFragment : Fragment() { @UnstableApi private fun addLocalFolderResult(uri: Uri?) { if (uri == null) return -// Observable.fromCallable { addLocalFolder(uri) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { feed: Feed -> -// val fragment: Fragment = FeedItemlistFragment.newInstance(feed.id) -// (getActivity() as MainActivity).loadChildFragment(fragment) -// }, { error: Throwable -> -// Log.e(TAG, Log.getStackTraceString(error)) -// (getActivity() as MainActivity) -// .showSnackbarAbovePlayer(error.localizedMessage, Snackbar.LENGTH_LONG) -// }) - val scope = CoroutineScope(Dispatchers.Main) scope.launch { try { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 8c2ee71d..0f7234b6 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -185,35 +185,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar // fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) { // if (controller == null) return // updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration)) -// } - -// private fun loadMediaInfo0(includingChapters: Boolean) { -// Logd(TAG, "loadMediaInfo called") -// -// val theMedia = controller?.getMedia() ?: return -// Logd(TAG, "loadMediaInfo $theMedia") -// -// if (currentMedia == null || theMedia.getIdentifier() != currentMedia?.getIdentifier()) { -// Logd(TAG, "loadMediaInfo loading details") -// disposable?.dispose() -// disposable = Maybe.create { emitter: MaybeEmitter -> -// val media: Playable? = theMedia -// if (media != null) { -// if (includingChapters) ChapterUtils.loadChapters(media, requireContext(), false) -// emitter.onSuccess(media) -// } else emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ media: Playable -> -// currentMedia = media -// updateUi(media) -// playerFragment1?.updateUi(media) -// playerFragment2?.updateUi(media) -// if (!includingChapters) loadMediaInfo(true) -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }, -// { updateUi(null) }) -// } // } fun loadMediaInfo(includingChapters: Boolean) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/BaseEpisodesListFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/BaseEpisodesListFragment.kt index 784fe542..cd94af25 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/BaseEpisodesListFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/BaseEpisodesListFragment.kt @@ -251,23 +251,6 @@ import kotlinx.coroutines.withContext @UnstableApi private fun performMultiSelectAction(actionItemId: Int) { val handler = EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId) -// Completable.fromAction { -// handler.handleAction(listAdapter.selectedItems.filterIsInstance()) -// if (listAdapter.shouldSelectLazyLoadedItems()) { -// var applyPage = page + 1 -// var nextPage: List -// do { -// nextPage = loadMoreData(applyPage) -// handler.handleAction(nextPage) -// applyPage++ -// } while (nextPage.size == EPISODES_PER_PAGE) -// } -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ listAdapter.endSelectMode() }, -// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { @@ -313,26 +296,6 @@ import kotlinx.coroutines.withContext isLoadingMore = true listAdapter.setDummyViews(1) listAdapter.notifyItemInserted(listAdapter.itemCount - 1) -// disposable = Observable.fromCallable { loadMoreData(page) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ data: List -> -// if (data.size < EPISODES_PER_PAGE) hasMoreItems = false -// Logd(TAG, "loadMoreItems $page ${data.size}") -// episodes.addAll(data) -// listAdapter.setDummyViews(0) -// listAdapter.updateItems(episodes) -// if (listAdapter.shouldSelectLazyLoadedItems()) listAdapter.setSelected(episodes.size - data.size, episodes.size, true) -// -// }, { error: Throwable? -> -// listAdapter.setDummyViews(0) -// listAdapter.updateItems(emptyList()) -// Log.e(TAG, Log.getStackTraceString(error)) -// }, { -// // Make sure to not always load 2 pages at once -// recyclerView.post { isLoadingMore = false } -// }) - lifecycleScope.launch { try { val data = withContext(Dispatchers.IO) { @@ -457,30 +420,6 @@ import kotlinx.coroutines.withContext fun loadItems() { Logd(TAG, "loadItems() called") -// disposable?.dispose() - -// disposable = Observable.fromCallable { -// Pair(loadData().toMutableList(), loadTotalItemCount()) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { data: Pair, Int> -> -// val restoreScrollPosition = episodes.isEmpty() -// episodes = data.first -// hasMoreItems = !(page == 1 && episodes.size < EPISODES_PER_PAGE) -// progressBar.visibility = View.GONE -// listAdapter.setDummyViews(0) -// listAdapter.updateItems(episodes) -// listAdapter.setTotalNumberOfItems(data.second) -// if (restoreScrollPosition) recyclerView.restoreScrollPosition(getPrefName()) -// updateToolbar() -// }, { error: Throwable? -> -// listAdapter.setDummyViews(0) -// listAdapter.updateItems(emptyList()) -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - lifecycleScope.launch { try { val data = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ChaptersFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ChaptersFragment.kt index 36b5c7ef..39687725 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ChaptersFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ChaptersFragment.kt @@ -147,20 +147,6 @@ class ChaptersFragment : AppCompatDialogFragment() { } private fun loadMediaInfo(forceRefresh: Boolean) { -// disposable?.dispose() - -// disposable = Maybe.create { emitter: MaybeEmitter -> -// val media = controller!!.getMedia() -// if (media != null) { -// loadChapters(media, requireContext(), forceRefresh) -// emitter.onSuccess(media) -// } else emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ media: Any -> onMediaChanged(media as Playable) }, -// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { val media = withContext(Dispatchers.IO) { val media_ = controller!!.getMedia() diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt index 8db1d18c..0bc32465 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt @@ -178,24 +178,6 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { } val loader = ItunesTopListLoader(requireContext()) -// disposable = Observable.fromCallable { loader.loadToplist(country?:"", -// NUM_OF_TOP_PODCASTS, DBReader.getFeedList()) } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { podcasts: List? -> -// progressBar.visibility = View.GONE -// topList = podcasts -// updateData(topList) -// }, { error: Throwable -> -// Log.e(TAG, Log.getStackTraceString(error)) -// progressBar.visibility = View.GONE -// txtvError.text = error.message -// txtvError.visibility = View.VISIBLE -// butRetry.setOnClickListener { loadToplist(country) } -// butRetry.visibility = View.VISIBLE -// }) - lifecycleScope.launch { try { val podcasts = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadLogFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadLogFragment.kt index 175e3d89..683b0fa4 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadLogFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadLogFragment.kt @@ -108,18 +108,6 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To } private fun loadDownloadLog() { -// disposable?.dispose() - -// disposable = Observable.fromCallable { DBReader.getDownloadLog() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: List? -> -// if (result != null) { -// downloadLog = result -// adapter.setDownloadLog(downloadLog) -// } -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val result = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt index f4123406..983c3465 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DownloadsFragment.kt @@ -305,41 +305,7 @@ import java.util.* } private fun loadItems() { -// disposable?.dispose() - emptyView.hide() -// disposable = Observable.fromCallable { -// val sortOrder: SortOrder? = UserPreferences.downloadsSortedOrder -// val downloadedItems: List = DBReader.getEpisodes(0, Int.MAX_VALUE, -// FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder) -// -// val mediaUrls: MutableList = ArrayList() -// if (runningDownloads.isEmpty()) return@fromCallable downloadedItems -// -// for (url in runningDownloads) { -// if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) continue // Already in list -// mediaUrls.add(url) -// } -// val currentDownloads: MutableList = DBReader.getFeedItemsWithUrl(mediaUrls).toMutableList() -// currentDownloads.addAll(downloadedItems) -// currentDownloads -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: List -> -// items = result.toMutableList() -// adapter.setDummyViews(0) -// progressBar.visibility = View.GONE -// adapter.updateItems(result) -// refreshInfoBar() -// }, { error: Throwable? -> -// adapter.setDummyViews(0) -// adapter.updateItems(emptyList()) -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - -// val scope = CoroutineScope(Dispatchers.Main) lifecycleScope.launch { try { val result = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt index 9ec16326..13e92097 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt @@ -273,20 +273,6 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { @UnstableApi private fun reconnectLocalFolder(uri: Uri) { if (feed == null) return - -// Completable.fromAction { -// requireActivity().contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) -// val documentFile = DocumentFile.fromTreeUri(requireContext(), uri) -// requireNotNull(documentFile) { "Unable to retrieve document tree" } -// feed!!.download_url = Feed.PREFIX_LOCAL_FOLDER + uri.toString() -// DBTasks.updateFeed(requireContext(), feed!!, true) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ (activity as MainActivity).showSnackbarAbovePlayer(string.ok, Snackbar.LENGTH_SHORT) }, -// { error: Throwable -> (activity as MainActivity).showSnackbarAbovePlayer(error.localizedMessage, Snackbar.LENGTH_LONG) }) - -// val scope = CoroutineScope(Dispatchers.Main) lifecycleScope.launch { try { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt index e1770857..6291aa52 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt @@ -475,21 +475,6 @@ import java.util.concurrent.Semaphore } private fun showErrorDetails() { -// Maybe.fromCallable( -// Callable { -// val feedDownloadLog: List = DBReader.getFeedDownloadLog(feedID) -// if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) return@Callable null -// feedDownloadLog[0] -// }) -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { downloadStatus: DownloadResult -> -// DownloadLogDetailsDialog(requireContext(), downloadStatus).show() -// }, -// { error: Throwable -> error.printStackTrace() }, -// { DownloadLogFragment().show(childFragmentManager, null) }) - lifecycleScope.launch { val downloadResult = withContext(Dispatchers.IO) { val feedDownloadLog: List = DBReader.getFeedDownloadLog(feedID) @@ -521,47 +506,6 @@ import java.util.concurrent.Semaphore } @UnstableApi private fun loadItems() { -// disposable?.dispose() -// disposable = Observable.fromCallable { this.loadData() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: Feed? -> -// feed = result -// Logd(TAG, "loadItems subscribe called ${feed?.title}") -// if (feed != null) { -// var hasNonMediaItems = false -// for (item in feed!!.items) { -// if (item.media == null) { -// hasNonMediaItems = true -// break -// } -// } -// if (hasNonMediaItems) { -// ioScope.launch { -// if (!ttsReady) { -// initializeTTS(requireContext()) -// semaphore.acquire() -// } -// } -// } -// } -// swipeActions.setFilter(feed?.itemFilter) -// refreshHeaderView() -// binding.progressBar.visibility = View.GONE -// adapter.setDummyViews(0) -// if (feed != null) adapter.updateItems(feed!!.items) -// binding.header.counts.text = (feed?.items?.size?:0).toString() -// updateToolbar() -// }, { error: Throwable? -> -// feed = null -// refreshHeaderView() -// adapter.setDummyViews(0) -// adapter.updateItems(emptyList()) -// updateToolbar() -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - lifecycleScope.launch { try { feed = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt index 10892217..8e2c7849 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedSettingsFragment.kt @@ -65,17 +65,6 @@ class FeedSettingsFragment : Fragment() { .replace(R.id.settings_fragment_container, FeedSettingsPreferenceFragment.newInstance(feedId), "settings_fragment") .commitAllowingStateLoss() -// disposable = Maybe.create(MaybeOnSubscribe { emitter: MaybeEmitter -> -// val feed = DBReader.getFeed(feedId) -// if (feed != null) emitter.onSuccess(feed) -// else emitter.onComplete() -// } as MaybeOnSubscribe) -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: Feed -> toolbar.subtitle = result.title }, -// { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) }, -// {}) - lifecycleScope.launch { val feed = withContext(Dispatchers.IO) { DBReader.getFeed(feedId) @@ -136,42 +125,6 @@ class FeedSettingsFragment : Fragment() { findPreference(PREF_SCREEN)!!.isVisible = false val feedId = requireArguments().getLong(EXTRA_FEED_ID) -// disposable = Maybe.create { emitter: MaybeEmitter -> -// val feed = DBReader.getFeed(feedId) -// if (feed != null) emitter.onSuccess(feed) -// else emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: Feed? -> -// feed = result -// feedPreferences = feed!!.preferences -// -// setupAutoDownloadGlobalPreference() -// setupAutoDownloadPreference() -// setupKeepUpdatedPreference() -// setupAutoDeletePreference() -// setupVolumeAdaptationPreferences() -//// setupNewEpisodesAction() -// setupAuthentificationPreference() -// setupEpisodeFilterPreference() -// setupPlaybackSpeedPreference() -// setupFeedAutoSkipPreference() -//// setupEpisodeNotificationPreference() -// setupTags() -// -// updateAutoDeleteSummary() -// updateVolumeAdaptationValue() -// updateAutoDownloadEnabled() -//// updateNewEpisodesAction() -// -// if (feed!!.isLocalFeed) { -// findPreference(PREF_AUTHENTICATION)!!.isVisible = false -// findPreference(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false -// } -// findPreference(PREF_SCREEN)!!.isVisible = true -// }, { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) }, {}) - lifecycleScope.launch { feed = withContext(Dispatchers.IO) { DBReader.getFeed(feedId) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt index d42cf24e..a2f375b5 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt @@ -285,17 +285,6 @@ import kotlin.concurrent.Volatile .withInitiatedByUser(true) .build() -// download = Observable.fromCallable { -// feeds = DBReader.getFeedList() -// downloader = HttpDownloader(request) -// downloader?.call() -// downloader?.result -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ status: DownloadResult? -> if (request.destination != null) checkDownloadResult(status, request.destination) }, -// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val status = withContext(Dispatchers.IO) { @@ -352,15 +341,6 @@ import kotlin.concurrent.Volatile } fun onFeedListChanged(event: FlowEvent.FeedListUpdateEvent) { -// updater = Observable.fromCallable { DBReader.getFeedList() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { feeds: List? -> -// this@OnlineFeedViewFragment.feeds = feeds -// handleUpdatedFeedStatus() -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) } -// ) lifecycleScope.launch { try { val feeds = withContext(Dispatchers.IO) { @@ -380,23 +360,6 @@ import kotlin.concurrent.Volatile @OptIn(UnstableApi::class) private fun parseFeed(destination: String) { Logd(TAG, "Parsing feed") -// parser = Maybe.fromCallable { doParseFeed(destination) } -// .subscribeOn(Schedulers.computation()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribeWith(object : DisposableMaybeObserver() { -// @UnstableApi override fun onSuccess(result: FeedHandlerResult) { -// showFeedInformation(result.feed, result.alternateFeedUrls) -// } -// -// override fun onComplete() { -// // Ignore null result: We showed the discovery dialog. -// } -// -// override fun onError(error: Throwable) { -// showErrorDialog(error.message, "") -// Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(error)) -// } -// }) lifecycleScope.launch { try { val result = withContext(Dispatchers.Default) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt index 562de501..393801ba 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt @@ -139,48 +139,6 @@ class PlayerDetailsFragment : Fragment() { return shownoteView.onContextItemSelected(item) } -// @UnstableApi private fun load0() { -// webViewLoader?.dispose() -// -// val context = context ?: return -// webViewLoader = Maybe.create { emitter: MaybeEmitter -> -// if (item == null) { -// media = controller?.getMedia() -// if (media == null) { -// emitter.onComplete() -// return@create -// } -// if (media is FeedMedia) { -// val feedMedia = media as FeedMedia -// item = feedMedia.item -// item?.setDescription(null) -// showHomeText = false -// homeText = null -// } -// } -// if (item != null) { -// media = item!!.media -// if (item!!.description == null) DBReader.loadTextDetailsOfFeedItem(item!!) -// if (prevItem?.itemIdentifier != item!!.itemIdentifier) cleanedNotes = null -// if (cleanedNotes == null) { -// Logd(TAG, "calling load description ${item!!.description==null} ${item!!.title}") -// val shownotesCleaner = ShownotesCleaner(context, item?.description ?: "", media?.getDuration()?:0) -// cleanedNotes = shownotesCleaner.processShownotes() -// } -// prevItem = item -// emitter.onSuccess(cleanedNotes?:"") -// } else emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ data: String? -> -// Logd(TAG, "subscribe: ${media?.getEpisodeTitle()}") -// displayMediaInfo(media!!) -// shownoteView.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html", "utf-8", "about:blank") -// Logd(TAG, "Webview loaded") -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) -// } - private fun load() { val context = context ?: return lifecycleScope.launch { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt index aefb22f9..54aa1a47 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt @@ -495,22 +495,8 @@ import java.util.* private fun loadItems(restoreScrollPosition: Boolean) { Logd(TAG, "loadItems() called") -// disposable?.dispose() - if (queue.isEmpty()) emptyView.hide() -// disposable = Observable.fromCallable { DBReader.getQueue().toMutableList() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ items: MutableList -> -// queue = items -// progressBar.visibility = View.GONE -// recyclerAdapter?.setDummyViews(0) -// recyclerAdapter?.updateItems(queue) -// if (restoreScrollPosition) recyclerView.restoreScrollPosition(TAG) -// refreshInfoBar() -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { queue = withContext(Dispatchers.IO) { DBReader.getQueue().toMutableList() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QuickFeedDiscoveryFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QuickFeedDiscoveryFragment.kt index dc32cb30..73f0c3db 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QuickFeedDiscoveryFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QuickFeedDiscoveryFragment.kt @@ -135,31 +135,6 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { return } -// disposable = Observable.fromCallable { -// loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList()) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { podcasts: List -> -// errorView.visibility = View.GONE -// if (podcasts.isEmpty()) { -// errorTextView.text = resources.getText(R.string.search_status_no_results) -// errorView.visibility = View.VISIBLE -// discoverGridLayout.visibility = View.INVISIBLE -// } else { -// discoverGridLayout.visibility = View.VISIBLE -// adapter.updateData(podcasts) -// } -// }, { error: Throwable -> -// Log.e(TAG, Log.getStackTraceString(error)) -// errorTextView.text = error.localizedMessage -// errorView.visibility = View.VISIBLE -// discoverGridLayout.visibility = View.INVISIBLE -// errorRetry.visibility = View.VISIBLE -// errorRetry.setOnClickListener { loadToplist() } -// }) - lifecycleScope.launch { try { val podcasts = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/SearchFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/SearchFragment.kt index 0fde744d..2a2e3da7 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/SearchFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/SearchFragment.kt @@ -305,28 +305,8 @@ import kotlinx.coroutines.withContext } @UnstableApi private fun search() { -// disposable?.dispose() - adapterFeeds.setEndButton(R.string.search_online) { this.searchOnline() } chip.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE -// disposable = Observable.fromCallable { this.performSearch() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ results: Pair?, List?> -> -// progressBar.visibility = View.GONE -// if (results.first != null) { -// this.results = results.first!!.toMutableList() -// adapter.updateItems(results.first!!) -// } -// if (requireArguments().getLong(ARG_FEED, 0) == 0L) { -// if (results.second != null) adapterFeeds.updateData(results.second!!.filterNotNull()) -// } else adapterFeeds.updateData(emptyList()) -// -// if (searchView.query.toString().isEmpty()) emptyViewHandler.setMessage(R.string.type_to_search) -// else emptyViewHandler.setMessage(getString(R.string.no_results_for_query) + searchView.query) -// -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val results = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/SubscriptionFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/SubscriptionFragment.kt index e214d919..93c8f370 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/SubscriptionFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/SubscriptionFragment.kt @@ -290,29 +290,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select } private fun loadSubscriptions() { -// disposable?.dispose() emptyView.hide() -// disposable = Observable.fromCallable { -// val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) -// val items: List = data.items -// items -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// { result: List -> -// // We have fewer items. This can result in items being selected that are no longer visible. -// if ( feedListFiltered.size > result.size) subscriptionAdapter.endSelectMode() -// feedList = result -// filterOnTag() -// progressBar.visibility = View.GONE -// subscriptionAdapter.setItems(feedListFiltered) -// feedCount.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() -// emptyView.updateVisibility() -// }, { error: Throwable? -> -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - lifecycleScope.launch { try { val result = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt index 062c1f05..83ab5e94 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/VideoEpisodeFragment.kt @@ -217,28 +217,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { } @UnstableApi private fun load() { -// disposable?.dispose() Logd(TAG, "load() called") - -// disposable = Observable.fromCallable { this.loadInBackground() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: FeedItem? -> -// item = result -// Logd(TAG, "load() item ${item?.id}") -// if (item != null) { -// val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE) -// if (isFavorite != isFav) { -// isFavorite = isFav -// invalidateOptionsMenu(requireActivity()) -// } -// } -// onFragmentLoaded() -// itemsLoaded = true -// }, { error: Throwable? -> -// Log.e(TAG, Log.getStackTraceString(error)) -// }) - lifecycleScope.launch { try { item = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt index e8acd124..bc9ec50f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/statistics/StatisticsFragment.kt @@ -106,12 +106,6 @@ class StatisticsFragment : PagedToolbarFragment() { .putLong(PREF_FILTER_TO, Long.MAX_VALUE) .apply() -// val disposable = Completable.fromFuture(DBWriter.resetStatistics()) -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ EventFlow.postEvent(FlowEvent.StatisticsEvent()) }, -// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/statistics/downloads/DownloadStatisticsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/statistics/downloads/DownloadStatisticsFragment.kt index 5713f08d..f4d99c41 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/statistics/downloads/DownloadStatisticsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/statistics/downloads/DownloadStatisticsFragment.kt @@ -70,24 +70,6 @@ class DownloadStatisticsFragment : Fragment() { } private fun loadStatistics() { -// disposable?.dispose() - -// disposable = Observable.fromCallable { -// // Filters do not matter here -// val statisticsData = DBReader.getStatistics(false, 0, Long.MAX_VALUE) -// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem -> -// item2.totalDownloadSize.compareTo(item1.totalDownloadSize) -// } -// statisticsData -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: StatisticsResult -> -// listAdapter.update(result.feedTime) -// progressBar.visibility = View.GONE -// downloadStatisticsList.visibility = View.VISIBLE -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val statisticsData = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/statistics/feed/FeedStatisticsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/statistics/feed/FeedStatisticsFragment.kt index f35de616..a6412b85 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/statistics/feed/FeedStatisticsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/statistics/feed/FeedStatisticsFragment.kt @@ -42,24 +42,6 @@ class FeedStatisticsFragment : Fragment() { } private fun loadStatistics() { -// disposable = Observable.fromCallable { -// val statisticsData = DBReader.getStatistics(true, 0, Long.MAX_VALUE) -// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem -> -// java.lang.Long.compare(item2.timePlayed, -// item1.timePlayed) -// } -// -// for (statisticsItem in statisticsData.feedTime) { -// if (statisticsItem.feed.id == feedId) { -// return@fromCallable statisticsItem -// } -// } -// null -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ s: StatisticsItem? -> this.showStats(s) }, { obj: Throwable -> obj.printStackTrace() }) - lifecycleScope.launch { try { val statisticsData = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/statistics/subscriptions/SubscriptionStatisticsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/statistics/subscriptions/SubscriptionStatisticsFragment.kt index a00c5478..fcec5d4a 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/statistics/subscriptions/SubscriptionStatisticsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/statistics/subscriptions/SubscriptionStatisticsFragment.kt @@ -119,35 +119,11 @@ class SubscriptionStatisticsFragment : Fragment() { } private fun loadStatistics() { -// disposable?.dispose() - // val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE) val includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false) val timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0) val timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE) -// disposable = Observable.fromCallable { -// val statisticsData = DBReader.getStatistics( -// includeMarkedAsPlayed, timeFilterFrom, timeFilterTo) -// statisticsData.feedTime.sortWith { item1: StatisticsItem, item2: StatisticsItem -> -// item2.timePlayed.compareTo(item1.timePlayed) -// } -// statisticsData -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: StatisticsResult -> -// statisticsResult = result -// // When "from" is "today", set it to today -// listAdapter.setTimeFilter(includeMarkedAsPlayed, max( -// min(timeFilterFrom.toDouble(), System.currentTimeMillis().toDouble()), result.oldestDate.toDouble()) -// .toLong(), -// min(timeFilterTo.toDouble(), System.currentTimeMillis().toDouble()).toLong()) -// listAdapter.update(result.feedTime) -// progressBar.visibility = View.GONE -// feedStatisticsList.visibility = View.VISIBLE -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val statisticsData = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/statistics/years/YearsStatisticsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/statistics/years/YearsStatisticsFragment.kt index 2aaa456a..4bfef2af 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/statistics/years/YearsStatisticsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/statistics/years/YearsStatisticsFragment.kt @@ -91,17 +91,6 @@ class YearsStatisticsFragment : Fragment() { } private fun loadStatistics() { -// disposable?.dispose() - -// disposable = Observable.fromCallable { DBReader.getMonthlyTimeStatistics() } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ result: List -> -// listAdapter.update(result) -// progressBar.visibility = View.GONE -// yearStatisticsList.visibility = View.VISIBLE -// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - lifecycleScope.launch { try { val result: List = withContext(Dispatchers.IO) { diff --git a/app/src/main/res/layout/edit_text_dialog.xml b/app/src/main/res/layout/edit_text_dialog.xml index c9c13fcb..6a62ef4a 100644 --- a/app/src/main/res/layout/edit_text_dialog.xml +++ b/app/src/main/res/layout/edit_text_dialog.xml @@ -8,7 +8,7 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 66b3fefa..315c7211 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -594,6 +594,7 @@ Database OPML HTML + Progress Show your subscriptions to a friend Transfer your subscriptions to another podcast app Import your subscriptions from another podcast app @@ -607,6 +608,11 @@ No file selected! Select all Deselect all + Episodes progress export + Episodes progress import + Transfer Podcini episodes history to Podcini on another device + Import Podcini episodes history from another device + Importing episodes progress will replace all of your current playing history. You should export your current progress as a backup. Do you want to replace\? OPML export HTML export Preferences export diff --git a/app/src/main/res/xml/preferences_import_export.xml b/app/src/main/res/xml/preferences_import_export.xml index 60c97e87..125c4678 100644 --- a/app/src/main/res/xml/preferences_import_export.xml +++ b/app/src/main/res/xml/preferences_import_export.xml @@ -31,13 +31,24 @@ + android:key="prefOpmlExport" + android:title="@string/opml_export_label" + android:summary="@string/opml_export_summary"/> + android:key="prefOpmlImport" + android:title="@string/opml_import_label" + android:summary="@string/opml_import_summary"/> + + + + + diff --git a/changelog.md b/changelog.md index 2105ce8b..5acc3011 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,13 @@ +## 5.5.0 + +* likely fixed Nextcloud Gpoddersync fails +* fixed text not accepted issue in "add podcast using rss feed" +* removed kotlin-stdlib dependency to improve build speed +* cleaned out the commented-out RxJava code +* added export/import of episode progress for migration to future versions. the exported content is the same as with instant sync: all the play progress of episodes (completed or not) +* this is likely the last release of Podcini 5, sauf perhaps any minor bugfixes. +* the next Podcini overhauls the entire DB routines, SQLite is replaced with the object-based Realm, and is not compatible with version 5 and below. + ## 5.4.2 * likely fixed crash issue when the app is restarted after long idle diff --git a/fastlane/metadata/android/en-US/changelogs/3020150.txt b/fastlane/metadata/android/en-US/changelogs/3020150.txt new file mode 100644 index 00000000..aa815205 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020150.txt @@ -0,0 +1,10 @@ + +Version 5.5.0 brings several changes: + +* likely fixed Nextcloud Gpoddersync fails +* fixed text not accepted issue in "add podcast using rss feed" +* removed kotlin-stdlib dependency to improve build speed +* cleaned out the commented-out RxJava code +* added export/import of episode progress for migration to future versions. the exported content is the same as with instant sync: all the play progress of episodes (completed or not) +* this is likely the last release of Podcini 5, sauf perhaps any minor bugfixes. +* the next Podcini overhauls the entire DB routines, SQLite is replaced with the object-based Realm, and is not compatible with version 5 and below.