5.5.0 commit

This commit is contained in:
Xilin Jia 2024-06-10 15:56:20 +01:00
parent dc144a5b4a
commit 2c23d9fc7e
50 changed files with 287 additions and 958 deletions

View File

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

View File

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

View File

@ -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<List<WorkInfo>> = WorkManager.getInstance(context).getWorkInfosByTag(tag)
// Observable.fromFuture(future)
// .subscribeOn(Schedulers.io())
// .observeOn(Schedulers.io())
// .subscribe(
// { workInfos: List<WorkInfo> ->
// 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String, Uri>(BackupDatabase()) { uri: Uri? -> this.backupDatabaseResult(uri) }
private val chooseOpmlImportPathLauncher = registerForActivityResult<String, Uri>(ActivityResultContracts.GetContent()) { uri: Uri? ->
this.chooseOpmlImportPathResult(uri) }
private val chooseOpmlImportPathLauncher = registerForActivityResult<String, Uri>(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<Preference>(PREF_PROGRESS_EXPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
openExportPathPicker(Export.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter())
true
}
findPreference<Preference>(PREF_PROGRESS_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
importEpisodeProgress()
true
}
findPreference<Preference>(PREF_OPML_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
try {
chooseOpmlImportPathLauncher.launch("*/*")
@ -123,11 +131,11 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
exportDatabase()
true
}
findPreference<Preference>(PREF_PREFERENCES_IMPORT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
findPreference<Preference>(PREF_PREFERENCES_IMPORT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
importPreferences()
true
}
findPreference<Preference>(PREF_PREFERENCES_EXPORT)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
findPreference<Preference>(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"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Feed?>?, 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<FeedItem> = favoriteByFeed[feedId]!!
writer.append("<li><div>\n")
writeFeed(writer, favorites[0].feed, feedTemplate)
writer.append("<ul>\n")
for (item in favorites) {
writeFavoriteItem(writer, item, favTemplate)
}
writer.append("</ul></div></li>\n")
}
writer.append(templateParts[1])
Logd(TAG, "Finished writing document")
}
@ -62,18 +56,14 @@ class FavoritesWriter : ExportWriter {
*/
private fun getFeedMap(favoritesList: List<FeedItem>): Map<Long, MutableList<FeedItem>> {
val feedMap: MutableMap<Long, MutableList<FeedItem>> = 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)
}

View File

@ -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<EpisodeAction>()
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<FeedItem> = 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<Long, FeedItem>? {
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)
}
}

View File

@ -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<Feed?>?, writer: Writer?, context: Context) {
Logd(TAG, "Starting to write document")
val queuedEpisodeActions: MutableList<EpisodeAction> = 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<FeedItem>()
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"
}
}

View File

@ -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<OpmlElement>? ->
// 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!!)

View File

@ -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<Feed> ->
// listItems = result
// val titles = ArrayList<String>()
// for (feed in result) {
// if (feed.title != null) titles.add(feed.title!!)
// }
// val adapter: ArrayAdapter<String> = ArrayAdapter<String>(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) {

View File

@ -24,26 +24,6 @@ class SplashActivity : Activity() {
val content = findViewById<View>(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 {

View File

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

View File

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

View File

@ -88,19 +88,6 @@ class TagSettingsDialog : DialogFragment() {
}
private fun loadTags() {
// Observable.fromCallable {
// DBReader.getTags()
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<String> ->
// 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 {

View File

@ -164,19 +164,6 @@ class AddFeedFragment : Fragment() {
@UnstableApi private fun addLocalFolderResult(uri: Uri?) {
if (uri == null) return
// Observable.fromCallable<Feed> { 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 {

View File

@ -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<Playable> { emitter: MaybeEmitter<Playable?> ->
// 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) {

View File

@ -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<FeedItem>())
// if (listAdapter.shouldSelectLazyLoadedItems()) {
// var applyPage = page + 1
// var nextPage: List<FeedItem>
// 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<FeedItem> ->
// 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<MutableList<FeedItem>, 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) {

View File

@ -147,20 +147,6 @@ class ChaptersFragment : AppCompatDialogFragment() {
}
private fun loadMediaInfo(forceRefresh: Boolean) {
// disposable?.dispose()
// disposable = Maybe.create { emitter: MaybeEmitter<Any> ->
// 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()

View File

@ -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<PodcastSearchResult>? ->
// 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) {

View File

@ -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<DownloadResult>? ->
// if (result != null) {
// downloadLog = result
// adapter.setDownloadLog(downloadLog)
// }
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
lifecycleScope.launch {
try {
val result = withContext(Dispatchers.IO) {

View File

@ -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<FeedItem> = DBReader.getEpisodes(0, Int.MAX_VALUE,
// FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)
//
// val mediaUrls: MutableList<String> = 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<FeedItem> = DBReader.getFeedItemsWithUrl(mediaUrls).toMutableList()
// currentDownloads.addAll(downloadedItems)
// currentDownloads
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<FeedItem> ->
// 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) {

View File

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

View File

@ -475,21 +475,6 @@ import java.util.concurrent.Semaphore
}
private fun showErrorDetails() {
// Maybe.fromCallable<DownloadResult>(
// Callable {
// val feedDownloadLog: List<DownloadResult> = 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<DownloadResult> = DBReader.getFeedDownloadLog(feedID)
@ -521,47 +506,6 @@ import java.util.concurrent.Semaphore
}
@UnstableApi private fun loadItems() {
// disposable?.dispose()
// disposable = Observable.fromCallable<Feed?> { 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) {

View File

@ -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<Feed> ->
// val feed = DBReader.getFeed(feedId)
// if (feed != null) emitter.onSuccess(feed)
// else emitter.onComplete()
// } as MaybeOnSubscribe<Feed>)
// .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<Preference>(PREF_SCREEN)!!.isVisible = false
val feedId = requireArguments().getLong(EXTRA_FEED_ID)
// disposable = Maybe.create { emitter: MaybeEmitter<Feed?> ->
// 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<Preference>(PREF_AUTHENTICATION)!!.isVisible = false
// findPreference<Preference>(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false
// }
// findPreference<Preference>(PREF_SCREEN)!!.isVisible = true
// }, { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) }, {})
lifecycleScope.launch {
feed = withContext(Dispatchers.IO) {
DBReader.getFeed(feedId)

View File

@ -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<Feed>? ->
// 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<FeedHandlerResult?>() {
// @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) {

View File

@ -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<String?> ->
// 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 {

View File

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

View File

@ -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<PodcastSearchResult> ->
// 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) {

View File

@ -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<FeedItem>?, List<Feed?>?> ->
// 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) {

View File

@ -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<NavDrawerData.FeedDrawerItem> = data.items
// items
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<NavDrawerData.FeedDrawerItem> ->
// // 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) {

View File

@ -217,28 +217,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
}
@UnstableApi private fun load() {
// disposable?.dispose()
Logd(TAG, "load() called")
// disposable = Observable.fromCallable<FeedItem?> { 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) {

View File

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

View File

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

View File

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

View File

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

View File

@ -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<MonthlyStatisticsItem> ->
// 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<MonthlyStatisticsItem> = withContext(Dispatchers.IO) {

View File

@ -8,7 +8,7 @@
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text|numberDecimal"
android:inputType="text"
android:ems="10"
android:id="@+id/editText" />

View File

@ -594,6 +594,7 @@
<string name="database">Database</string>
<string name="opml">OPML</string>
<string name="html">HTML</string>
<string name="progress">Progress</string>
<string name="html_export_summary">Show your subscriptions to a friend</string>
<string name="opml_export_summary">Transfer your subscriptions to another podcast app</string>
<string name="opml_import_summary">Import your subscriptions from another podcast app</string>
@ -607,6 +608,11 @@
<string name="opml_import_error_no_file">No file selected!</string>
<string name="select_all_label">Select all</string>
<string name="deselect_all_label">Deselect all</string>
<string name="progress_export_label">Episodes progress export</string>
<string name="progress_import_label">Episodes progress import</string>
<string name="progress_export_summary">Transfer Podcini episodes history to Podcini on another device</string>
<string name="progress_import_summary">Import Podcini episodes history from another device</string>
<string name="progress_import_warning">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\?</string>
<string name="opml_export_label">OPML export</string>
<string name="html_export_label">HTML export</string>
<string name="preferences_export_label">Preferences export</string>

View File

@ -40,6 +40,17 @@
android:summary="@string/opml_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/progress">
<Preference
android:key="prefProgressExport"
android:title="@string/progress_export_label"
android:summary="@string/progress_export_summary"/>
<Preference
android:key="prefProgressImport"
android:title="@string/progress_import_label"
android:summary="@string/progress_import_summary"/>
</PreferenceCategory>
<PreferenceCategory android:title="@string/html">
<Preference
android:key="prefHtmlExport"

View File

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

View File

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