diff --git a/app/build.gradle b/app/build.gradle index 29761d1e..f4ba9142 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 3020142 - versionName "5.1.0" + versionCode 3020143 + versionName "5.2.0" def commit = "" try { @@ -262,10 +262,6 @@ dependencies { implementation "io.coil-kt:coil:2.6.0" -// implementation "com.github.bumptech.glide:glide:4.16.0" -// implementation "com.github.bumptech.glide:okhttp3-integration:4.16.0@aar" -// kapt "com.github.bumptech.glide:ksp:4.16.0" - implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0" implementation 'com.squareup.okio:okio:3.9.0' diff --git a/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt b/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt index 85d743b5..397dc531 100644 --- a/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt +++ b/app/src/androidTest/java/ac/test/podcini/service/download/HttpDownloaderTest.kt @@ -9,6 +9,7 @@ import ac.mdiq.podcini.storage.model.download.DownloadError import ac.mdiq.podcini.storage.model.feed.FeedFile import ac.mdiq.podcini.net.download.serviceinterface.DownloadRequest import ac.mdiq.podcini.preferences.UserPreferences.init +import ac.mdiq.podcini.util.Logd import de.test.podcini.util.service.download.HTTPBin import org.junit.After import org.junit.Assert @@ -54,7 +55,7 @@ class HttpDownloaderTest { val fileUrl = File(destDir, title).absolutePath val file = File(fileUrl) if (deleteExisting) { - Log.d(TAG, "Deleting file: " + file.delete()) + Logd(TAG, "Deleting file: " + file.delete()) } feedfile.setFile_url(fileUrl) return feedfile diff --git a/app/src/main/java/ac/mdiq/podcini/feed/ChapterMerger.kt b/app/src/main/java/ac/mdiq/podcini/feed/ChapterMerger.kt index ef7f70ea..d8ecf065 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/ChapterMerger.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/ChapterMerger.kt @@ -2,6 +2,7 @@ package ac.mdiq.podcini.feed import android.util.Log import ac.mdiq.podcini.storage.model.feed.Chapter +import ac.mdiq.podcini.util.Logd import kotlin.math.abs object ChapterMerger { @@ -11,7 +12,7 @@ object ChapterMerger { * This method might modify the input data. */ fun merge(chapters1: List?, chapters2: List?): List? { - Log.d(TAG, "Merging chapters") + Logd(TAG, "Merging chapters") when { chapters1 == null -> return chapters2 chapters2 == null -> return chapters1 diff --git a/app/src/main/java/ac/mdiq/podcini/feed/LocalFeedUpdater.kt b/app/src/main/java/ac/mdiq/podcini/feed/LocalFeedUpdater.kt index 077aa8d9..6346a726 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/LocalFeedUpdater.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/LocalFeedUpdater.kt @@ -25,6 +25,7 @@ import ac.mdiq.podcini.feed.parser.media.id3.ID3ReaderException import ac.mdiq.podcini.feed.parser.media.id3.Id3MetadataReader import ac.mdiq.podcini.feed.parser.media.vorbis.VorbisCommentMetadataReader import ac.mdiq.podcini.feed.parser.media.vorbis.VorbisCommentReaderException +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.input.CountingInputStream import java.io.BufferedInputStream import java.io.IOException @@ -186,8 +187,7 @@ object LocalFeedUpdater { item.setDescriptionIfLonger(reader.comment) } } catch (e: IOException) { - Log.d(TAG, "Unable to parse ID3 of " + file.uri + ": " + e.message) - + Logd(TAG, "Unable to parse ID3 of " + file.uri + ": " + e.message) try { context.contentResolver.openInputStream(file.uri).use { inputStream -> val reader = VorbisCommentMetadataReader(inputStream) @@ -195,12 +195,12 @@ object LocalFeedUpdater { item.setDescriptionIfLonger(reader.description) } } catch (e2: IOException) { - Log.d(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) + Logd(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) } catch (e2: VorbisCommentReaderException) { - Log.d(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) + Logd(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) } } catch (e: ID3ReaderException) { - Log.d(TAG, "Unable to parse ID3 of " + file.uri + ": " + e.message) + Logd(TAG, "Unable to parse ID3 of " + file.uri + ": " + e.message) try { context.contentResolver.openInputStream(file.uri).use { inputStream -> @@ -209,9 +209,9 @@ object LocalFeedUpdater { item.setDescriptionIfLonger(reader.description) } } catch (e2: IOException) { - Log.d(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) + Logd(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) } catch (e2: VorbisCommentReaderException) { - Log.d(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) + Logd(TAG, "Unable to parse vorbis comments of " + file.uri + ": " + e2.message) } } } diff --git a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ChapterReader.kt b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ChapterReader.kt index e56ba036..2d33abef 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ChapterReader.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ChapterReader.kt @@ -4,6 +4,7 @@ import android.util.Log import ac.mdiq.podcini.storage.model.feed.Chapter import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage.Companion.makeUrl import ac.mdiq.podcini.feed.parser.media.id3.model.FrameHeader +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.input.CountingInputStream import java.io.IOException import java.net.URLDecoder @@ -18,9 +19,9 @@ class ChapterReader(input: CountingInputStream?) : ID3Reader(input!!) { @Throws(IOException::class, ID3ReaderException::class) override fun readFrame(frameHeader: FrameHeader) { if (FRAME_ID_CHAPTER == frameHeader.id) { - Log.d(TAG, "Handling frame: $frameHeader") + Logd(TAG, "Handling frame: $frameHeader") val chapter = readChapter(frameHeader) - Log.d(TAG, "Chapter done: $chapter") + Logd(TAG, "Chapter done: $chapter") chapters.add(chapter) } else { super.readFrame(frameHeader) @@ -48,12 +49,12 @@ class ChapterReader(input: CountingInputStream?) : ID3Reader(input!!) { @Throws(IOException::class, ID3ReaderException::class) fun readChapterSubFrame(frameHeader: FrameHeader, chapter: Chapter) { - Log.d(TAG, "Handling subframe: $frameHeader") + Logd(TAG, "Handling subframe: $frameHeader") val frameStartPosition = position when (frameHeader.id) { FRAME_ID_TITLE -> { chapter.title = readEncodingAndString(frameHeader.size) - Log.d(TAG, "Found title: " + chapter.title) + Logd(TAG, "Found title: " + chapter.title) } FRAME_ID_LINK -> { readEncodingAndString(frameHeader.size) // skip description @@ -61,7 +62,7 @@ class ChapterReader(input: CountingInputStream?) : ID3Reader(input!!) { try { val decodedLink = URLDecoder.decode(url, "ISO-8859-1") chapter.link = decodedLink - Log.d(TAG, "Found link: " + chapter.link) + Logd(TAG, "Found link: " + chapter.link) } catch (iae: IllegalArgumentException) { Log.w(TAG, "Bad URL found in ID3 data") } @@ -71,10 +72,10 @@ class ChapterReader(input: CountingInputStream?) : ID3Reader(input!!) { val mime = readIsoStringNullTerminated(frameHeader.size) val type = readByte() val description = readEncodedString(encoding.toInt(), frameHeader.size) - Log.d(TAG, "Found apic: $mime,$description") + Logd(TAG, "Found apic: $mime,$description") if (MIME_IMAGE_URL == mime) { val link = readIsoStringNullTerminated(frameHeader.size) - Log.d(TAG, "Link: $link") + Logd(TAG, "Link: $link") if (chapter.imageUrl.isNullOrEmpty() || type.toInt() == IMAGE_TYPE_COVER) chapter.imageUrl = link } else { val alreadyConsumed = position - frameStartPosition @@ -82,7 +83,7 @@ class ChapterReader(input: CountingInputStream?) : ID3Reader(input!!) { if (chapter.imageUrl.isNullOrEmpty() || type.toInt() == IMAGE_TYPE_COVER) chapter.imageUrl = makeUrl(position, rawImageDataLength) } } - else -> Log.d(TAG, "Unknown chapter sub-frame.") + else -> Logd(TAG, "Unknown chapter sub-frame.") } // Skip garbage to fill frame completely // This also asserts that we are not reading too many bytes from this frame. diff --git a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ID3Reader.kt b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ID3Reader.kt index 2e38d1b9..75d1f0f4 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ID3Reader.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/id3/ID3Reader.kt @@ -3,6 +3,7 @@ package ac.mdiq.podcini.feed.parser.media.id3 import android.util.Log import ac.mdiq.podcini.feed.parser.media.id3.model.FrameHeader import ac.mdiq.podcini.feed.parser.media.id3.model.TagHeader +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.IOUtils import org.apache.commons.io.input.CountingInputStream import java.io.ByteArrayOutputStream @@ -25,7 +26,7 @@ open class ID3Reader(private val inputStream: CountingInputStream) { while (position < tagContentStartPosition + tagHeader!!.size) { val frameHeader = readFrameHeader() if (frameHeader.id[0] < '0' || frameHeader.id[0] > 'z') { - Log.d(TAG, "Stopping because of invalid frame: $frameHeader") + Logd(TAG, "Stopping because of invalid frame: $frameHeader") return } readFrame(frameHeader) @@ -34,7 +35,7 @@ open class ID3Reader(private val inputStream: CountingInputStream) { @Throws(IOException::class, ID3ReaderException::class) protected open fun readFrame(frameHeader: FrameHeader) { - Log.d(TAG, "Skipping frame: " + frameHeader.id + ", size: " + frameHeader.size) + Logd(TAG, "Skipping frame: " + frameHeader.id + ", size: " + frameHeader.size) skipBytes(frameHeader.size) } diff --git a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/vorbis/VorbisCommentReader.kt b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/vorbis/VorbisCommentReader.kt index 2c813f8a..01b37ae8 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/parser/media/vorbis/VorbisCommentReader.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/parser/media/vorbis/VorbisCommentReader.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.feed.parser.media.vorbis +import ac.mdiq.podcini.util.Logd import android.util.Log import org.apache.commons.io.EndianUtils import org.apache.commons.io.IOUtils @@ -17,12 +18,12 @@ abstract class VorbisCommentReader internal constructor(private val input: Input findOggPage() findCommentHeader() val commentHeader = readCommentHeader() - Log.d(TAG, commentHeader.toString()) + Logd(TAG, commentHeader.toString()) for (i in 0 until commentHeader.userCommentLength) { readUserComment() } } catch (e: IOException) { - Log.d(TAG, "Vorbis parser: " + e.message) + Logd(TAG, "Vorbis parser: " + e.message) } } @@ -54,7 +55,7 @@ abstract class VorbisCommentReader internal constructor(private val input: Input } val key = readContentVectorKey(vectorLength)!!.lowercase() val shouldReadValue = handles(key) - Log.d(TAG, "key=$key, length=$vectorLength, handles=$shouldReadValue") + Logd(TAG, "key=$key, length=$vectorLength, handles=$shouldReadValue") if (shouldReadValue) { val value = readUtf8String(vectorLength - key.length - 1) onContentVectorValue(key, value) diff --git a/app/src/main/java/ac/mdiq/podcini/feed/parser/util/DateUtils.kt b/app/src/main/java/ac/mdiq/podcini/feed/parser/util/DateUtils.kt index 14b3b18e..14fbb48e 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/parser/util/DateUtils.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/parser/util/DateUtils.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.feed.parser.util +import ac.mdiq.podcini.util.Logd import android.util.Log import org.apache.commons.lang3.StringUtils import java.text.ParsePosition @@ -98,7 +99,7 @@ object DateUtils { // if date string starts with a weekday, try parsing date string without it if (date.matches("^\\w+, .*$".toRegex())) return parse(date.substring(date.indexOf(',') + 1)) - Log.d(TAG, "Could not parse date string \"$input\" [$date]") + Logd(TAG, "Could not parse date string \"$input\" [$date]") return null } diff --git a/app/src/main/java/ac/mdiq/podcini/feed/util/PlaybackSpeedUtils.kt b/app/src/main/java/ac/mdiq/podcini/feed/util/PlaybackSpeedUtils.kt index 5ace8f92..1e594c70 100644 --- a/app/src/main/java/ac/mdiq/podcini/feed/util/PlaybackSpeedUtils.kt +++ b/app/src/main/java/ac/mdiq/podcini/feed/util/PlaybackSpeedUtils.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedPreferences import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.util.Logd /** * Utility class to use the appropriate playback speed based on [PlaybackPreferences] @@ -32,8 +33,8 @@ object PlaybackSpeedUtils { val feed = item.feed if (feed?.preferences != null) { playbackSpeed = feed.preferences!!.feedPlaybackSpeed - Log.d(TAG, "using feed speed $playbackSpeed") - } else Log.d(TAG, "Can not get feed specific playback speed: $feed") + Logd(TAG, "using feed speed $playbackSpeed") + } else Logd(TAG, "Can not get feed specific playback speed: $feed") } } } diff --git a/app/src/main/java/ac/mdiq/podcini/net/common/UrlChecker.kt b/app/src/main/java/ac/mdiq/podcini/net/common/UrlChecker.kt index 2aba6649..8f581485 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/common/UrlChecker.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/common/UrlChecker.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.net.common +import ac.mdiq.podcini.util.Logd import android.net.Uri import android.util.Log import okhttp3.HttpUrl.Companion.toHttpUrlOrNull @@ -29,23 +30,23 @@ object UrlChecker { val lowerCaseUrl = url.lowercase() // protocol names are case insensitive when { lowerCaseUrl.startsWith("feed://") -> { - Log.d(TAG, "Replacing feed:// with http://") + Logd(TAG, "Replacing feed:// with http://") return prepareUrl(url.substring("feed://".length)) } lowerCaseUrl.startsWith("pcast://") -> { - Log.d(TAG, "Removing pcast://") + Logd(TAG, "Removing pcast://") return prepareUrl(url.substring("pcast://".length)) } lowerCaseUrl.startsWith("pcast:") -> { - Log.d(TAG, "Removing pcast:") + Logd(TAG, "Removing pcast:") return prepareUrl(url.substring("pcast:".length)) } lowerCaseUrl.startsWith("itpc") -> { - Log.d(TAG, "Replacing itpc:// with http://") + Logd(TAG, "Replacing itpc:// with http://") return prepareUrl(url.substring("itpc://".length)) } lowerCaseUrl.startsWith(AP_SUBSCRIBE) -> { - Log.d(TAG, "Removing podcini-subscribe://") + Logd(TAG, "Removing podcini-subscribe://") return prepareUrl(url.substring(AP_SUBSCRIBE.length)) } // lowerCaseUrl.contains(AP_SUBSCRIBE_DEEPLINK) -> { @@ -58,7 +59,7 @@ object UrlChecker { // } // } !(lowerCaseUrl.startsWith("http://") || lowerCaseUrl.startsWith("https://")) -> { - Log.d(TAG, "Adding http:// at the beginning of the URL") + Logd(TAG, "Adding http:// at the beginning of the URL") return "http://$url" } else -> return url diff --git a/app/src/main/java/ac/mdiq/podcini/net/discovery/CombinedSearcher.kt b/app/src/main/java/ac/mdiq/podcini/net/discovery/CombinedSearcher.kt index bed41b5a..a1d756b7 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/discovery/CombinedSearcher.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/discovery/CombinedSearcher.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.net.discovery +import ac.mdiq.podcini.util.Logd import android.text.TextUtils import android.util.Log import io.reactivex.Single @@ -27,7 +28,7 @@ class CombinedSearcher : PodcastSearcher { singleResults[i] = e latch.countDown() }, { throwable: Throwable? -> - Log.d(TAG, Log.getStackTraceString(throwable)) + Logd(TAG, Log.getStackTraceString(throwable)) latch.countDown() } )) diff --git a/app/src/main/java/ac/mdiq/podcini/net/discovery/FyydPodcastSearcher.kt b/app/src/main/java/ac/mdiq/podcini/net/discovery/FyydPodcastSearcher.kt index 861091de..32b7b0f9 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/discovery/FyydPodcastSearcher.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/discovery/FyydPodcastSearcher.kt @@ -12,8 +12,7 @@ class FyydPodcastSearcher : PodcastSearcher { override fun search(query: String): Single?> { return Single.create { subscriber: SingleEmitter?> -> - val response = client.searchPodcasts( - query, 10) + val response = client.searchPodcasts(query, 10) .subscribeOn(Schedulers.io()) .blockingGet() val searchResults = ArrayList() diff --git a/app/src/main/java/ac/mdiq/podcini/net/discovery/ItunesTopListLoader.kt b/app/src/main/java/ac/mdiq/podcini/net/discovery/ItunesTopListLoader.kt index 0f88bf39..adc13366 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/discovery/ItunesTopListLoader.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/discovery/ItunesTopListLoader.kt @@ -6,6 +6,7 @@ import android.content.Context import android.util.Log import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd import okhttp3.CacheControl import okhttp3.OkHttpClient import okhttp3.Request @@ -36,7 +37,7 @@ class ItunesTopListLoader(private val context: Context) { @Throws(IOException::class) private fun getTopListFeed(client: OkHttpClient?, country: String): String { val url = "https://itunes.apple.com/%s/rss/toppodcasts/limit=$NUM_LOADED/explicit=true/json" - Log.d(TAG, "Feed URL " + String.format(url, country)) + Logd(TAG, "Feed URL " + String.format(url, country)) val httpReq: Request.Builder = Request.Builder() .cacheControl(CacheControl.Builder().maxStale(1, TimeUnit.DAYS).build()) .url(String.format(url, country)) diff --git a/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearcherRegistry.kt b/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearcherRegistry.kt index 5b0cd507..1a92568c 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearcherRegistry.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearcherRegistry.kt @@ -26,6 +26,14 @@ object PodcastSearcherRegistry { return Single.just(url) } +// fun lookupUrlCo(url: String): String { +// for (searchProviderInfo in searchProviders) { +// if (searchProviderInfo.searcher.javaClass != CombinedSearcher::class.java && searchProviderInfo.searcher.urlNeedsLookup(url)) +// return searchProviderInfo.searcher.lookupUrlCo(url) +// } +// return url +// } + fun urlNeedsLookup(url: String): Boolean { for (searchProviderInfo in searchProviders) { if (searchProviderInfo.searcher.javaClass != CombinedSearcher::class.java && searchProviderInfo.searcher.urlNeedsLookup(url)) return true diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/ConnectionStateMonitor.kt b/app/src/main/java/ac/mdiq/podcini/net/download/ConnectionStateMonitor.kt index 70558136..0e5eecf7 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/ConnectionStateMonitor.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/ConnectionStateMonitor.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.net.download +import ac.mdiq.podcini.util.Logd import android.content.Context import android.net.ConnectivityManager import android.net.ConnectivityManager.OnNetworkActiveListener @@ -17,7 +18,7 @@ class ConnectionStateMonitor .build() @UnstableApi override fun onNetworkActive() { - Log.d(TAG, "ConnectionStateMonitor::onNetworkActive network connection changed") + Logd(TAG, "ConnectionStateMonitor::onNetworkActive network connection changed") NetworkConnectionChangeHandler.networkChangedDetected() } diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/FeedUpdateManager.kt b/app/src/main/java/ac/mdiq/podcini/net/download/FeedUpdateManager.kt index 4782f137..96dc2d2f 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/FeedUpdateManager.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/FeedUpdateManager.kt @@ -15,6 +15,7 @@ import ac.mdiq.podcini.util.NetworkUtils.networkAvailable import ac.mdiq.podcini.util.event.MessageEvent import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.util.Logd import org.greenrobot.eventbus.EventBus import java.util.concurrent.TimeUnit @@ -68,7 +69,7 @@ object FeedUpdateManager { @JvmStatic @JvmOverloads fun runOnceOrAsk(context: Context, feed: Feed? = null) { - Log.d(TAG, "Run auto update immediately in background.") + Logd(TAG, "Run auto update immediately in background.") when { feed != null && feed.isLocalFeed -> runOnce(context, feed) !networkAvailable() -> EventBus.getDefault().post(MessageEvent(context.getString(R.string.download_error_no_connection))) diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/NetworkConnectionChangeHandler.kt b/app/src/main/java/ac/mdiq/podcini/net/download/NetworkConnectionChangeHandler.kt index 70342cef..ebb02991 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/NetworkConnectionChangeHandler.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/NetworkConnectionChangeHandler.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.DBTasks import ac.mdiq.podcini.util.NetworkUtils.isAutoDownloadAllowed import ac.mdiq.podcini.util.NetworkUtils.isNetworkRestricted import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface +import ac.mdiq.podcini.util.Logd @UnstableApi object NetworkConnectionChangeHandler { @@ -21,7 +22,7 @@ object NetworkConnectionChangeHandler { @JvmStatic fun networkChangedDetected() { if (isAutoDownloadAllowed) { - Log.d(TAG, "auto-dl network available, starting auto-download") + Logd(TAG, "auto-dl network available, starting auto-download") DBTasks.autodownloadUndownloadedItems(context) } else { // if new network is Wi-Fi, finish ongoing downloads, // otherwise cancel all downloads diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/service/BasicAuthorizationInterceptor.kt b/app/src/main/java/ac/mdiq/podcini/net/download/service/BasicAuthorizationInterceptor.kt index 554d7eba..e1f1499f 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/service/BasicAuthorizationInterceptor.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/service/BasicAuthorizationInterceptor.kt @@ -6,6 +6,7 @@ import ac.mdiq.podcini.net.download.service.HttpCredentialEncoder.encode import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.util.URIUtil import ac.mdiq.podcini.net.download.serviceinterface.DownloadRequest +import ac.mdiq.podcini.util.Logd import okhttp3.Interceptor import okhttp3.Interceptor.Chain import okhttp3.Request @@ -47,24 +48,24 @@ class BasicAuthorizationInterceptor : Interceptor { } else userInfo = DBReader.getImageAuthentication(request.url.toString()) if (userInfo.isEmpty()) { - Log.d(TAG, "no credentials for '" + request.url + "'") + Logd(TAG, "no credentials for '" + request.url + "'") return response } if (!userInfo.contains(":")) { - Log.d(TAG, "Invalid credentials for '" + request.url + "'") + Logd(TAG, "Invalid credentials for '" + request.url + "'") return response } val username = userInfo.substring(0, userInfo.indexOf(':')) val password = userInfo.substring(userInfo.indexOf(':') + 1) - Log.d(TAG, "Authorization failed, re-trying with ISO-8859-1 encoded credentials") + Logd(TAG, "Authorization failed, re-trying with ISO-8859-1 encoded credentials") newRequest.header(HEADER_AUTHORIZATION, encode(username, password, "ISO-8859-1")) response = chain.proceed(newRequest.build()) if (response.code != HttpURLConnection.HTTP_UNAUTHORIZED) return response - Log.d(TAG, "Authorization failed, re-trying with UTF-8 encoded credentials") + Logd(TAG, "Authorization failed, re-trying with UTF-8 encoded credentials") newRequest.header(HEADER_AUTHORIZATION, encode(username, password, "UTF-8")) return chain.proceed(newRequest.build()) } diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/service/FeedUpdateWorker.kt b/app/src/main/java/ac/mdiq/podcini/net/download/service/FeedUpdateWorker.kt index 6787c063..080fa887 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/service/FeedUpdateWorker.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/service/FeedUpdateWorker.kt @@ -28,6 +28,7 @@ import ac.mdiq.podcini.ui.utils.NotificationUtils import ac.mdiq.podcini.storage.model.download.DownloadError import ac.mdiq.podcini.storage.model.download.DownloadResult import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd import android.os.Build import java.util.* @@ -54,7 +55,7 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont toUpdate.shuffle() // If the worker gets cancelled early, every feed has a chance to be updated } else { val feed = DBReader.getFeed(feedId) ?: return Result.success() - Log.d(TAG, "doWork feed.download_url: ${feed.download_url}") + Logd(TAG, "doWork feed.download_url: ${feed.download_url}") if (!feed.isLocalFeed) allAreLocal = false toUpdate = ArrayList() toUpdate.add(feed) // Needs to be updatable, so no singletonList @@ -63,7 +64,7 @@ class FeedUpdateWorker(context: Context, params: WorkerParameters) : Worker(cont if (!inputData.getBoolean(FeedUpdateManager.EXTRA_EVEN_ON_MOBILE, false) && !allAreLocal) { if (!NetworkUtils.networkAvailable() || !NetworkUtils.isFeedRefreshAllowed) { - Log.d(TAG, "Blocking automatic update") + Logd(TAG, "Blocking automatic update") return Result.retry() } } diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/service/PodciniHttpClient.kt b/app/src/main/java/ac/mdiq/podcini/net/download/service/PodciniHttpClient.kt index c9301e08..21d5cab8 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/service/PodciniHttpClient.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/service/PodciniHttpClient.kt @@ -3,6 +3,7 @@ package ac.mdiq.podcini.net.download.service import android.util.Log import ac.mdiq.podcini.storage.model.download.ProxyConfig import ac.mdiq.podcini.net.ssl.SslClientSetup +import ac.mdiq.podcini.util.Logd import okhttp3.* import okhttp3.Credentials.basic import okhttp3.OkHttpClient.Builder @@ -50,7 +51,7 @@ object PodciniHttpClient { */ @JvmStatic fun newBuilder(): Builder { - Log.d(TAG, "Creating new instance of HTTP client") + Logd(TAG, "Creating new instance of HTTP client") System.setProperty("http.maxConnections", MAX_CONNECTIONS.toString()) diff --git a/app/src/main/java/ac/mdiq/podcini/net/download/service/handler/FeedParserTask.kt b/app/src/main/java/ac/mdiq/podcini/net/download/service/handler/FeedParserTask.kt index a6133805..649cced5 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/download/service/handler/FeedParserTask.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/download/service/handler/FeedParserTask.kt @@ -10,6 +10,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedPreferences import ac.mdiq.podcini.storage.model.feed.VolumeAdaptionSetting import ac.mdiq.podcini.net.download.serviceinterface.DownloadRequest import ac.mdiq.podcini.feed.parser.UnsupportedFeedtypeException +import ac.mdiq.podcini.util.Logd import org.xml.sax.SAXException import java.io.File import java.io.IOException @@ -44,7 +45,7 @@ class FeedParserTask(private val request: DownloadRequest) : Callable = getFeedListDownloadUrls() @@ -104,11 +105,11 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont val queuedRemovedFeeds: MutableList = synchronizationQueueStorage.queuedRemovedFeeds var queuedAddedFeeds: List = synchronizationQueueStorage.queuedAddedFeeds - Log.d(TAG, "Downloaded subscription changes: $subscriptionChanges") + Logd(TAG, "Downloaded subscription changes: $subscriptionChanges") if (subscriptionChanges != null) { for (downloadUrl in subscriptionChanges.added) { if (!downloadUrl.startsWith("http")) { // Also matches https - Log.d(TAG, "Skipping url: $downloadUrl") + Logd(TAG, "Skipping url: $downloadUrl") continue } if (!containsUrl(localSubscriptions, downloadUrl) && !queuedRemovedFeeds.contains(downloadUrl)) { @@ -125,7 +126,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont } if (lastSync == 0L) { - Log.d(TAG, "First sync. Adding all local subscriptions.") + Logd(TAG, "First sync. Adding all local subscriptions.") queuedAddedFeeds = localSubscriptions.toMutableList() queuedAddedFeeds.removeAll(subscriptionChanges.added) queuedRemovedFeeds.removeAll(subscriptionChanges.removed) @@ -133,8 +134,8 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont } if (queuedAddedFeeds.isNotEmpty() || queuedRemovedFeeds.size > 0) { - Log.d(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", ")) - Log.d(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", ")) + Logd(TAG, "Added: " + StringUtils.join(queuedAddedFeeds, ", ")) + Logd(TAG, "Removed: " + StringUtils.join(queuedRemovedFeeds, ", ")) LockingAsyncExecutor.lock.lock() try { @@ -149,7 +150,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont } private fun waitForDownloadServiceCompleted() { - Log.d(TAG, "waitForDownloadServiceCompleted called") + Logd(TAG, "waitForDownloadServiceCompleted called") EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_wait_for_downloads)) try { while (true) { @@ -179,7 +180,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont if (lastSync == 0L) { EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_upload_played)) val readItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.PLAYED), SortOrder.DATE_NEW_OLD) - Log.d(TAG, "First sync. Upload state for all " + readItems.size + " played episodes") + Logd(TAG, "First sync. Upload state for all " + readItems.size + " played episodes") for (item in readItems) { val media = item.media ?: continue val played = EpisodeAction.Builder(item, EpisodeAction.PLAY) @@ -194,10 +195,10 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont if (queuedEpisodeActions.isNotEmpty()) { LockingAsyncExecutor.lock.lock() try { - Log.d(TAG, "Uploading ${queuedEpisodeActions.size} actions: ${StringUtils.join(queuedEpisodeActions, ", ")}") + Logd(TAG, "Uploading ${queuedEpisodeActions.size} actions: ${StringUtils.join(queuedEpisodeActions, ", ")}") val postResponse = syncServiceImpl.uploadEpisodeActions(queuedEpisodeActions) newTimeStamp = postResponse?.timestamp?:0L - Log.d(TAG, "Upload episode response: $postResponse") + Logd(TAG, "Upload episode response: $postResponse") synchronizationQueueStorage.clearEpisodeActionQueue() } finally { LockingAsyncExecutor.lock.unlock() @@ -208,7 +209,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont @UnstableApi @Throws(SyncServiceException::class) private fun syncEpisodeActions(syncServiceImpl: ISyncService) { - Log.d(TAG, "syncEpisodeActions called") + Logd(TAG, "syncEpisodeActions called") var (lastSync, newTimeStamp) = getEpisodeActions(syncServiceImpl) // upload local actions @@ -230,18 +231,18 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont var idRemove = 0L feedItem.media!!.setPosition(action.position * 1000) if (hasAlmostEnded(feedItem.media!!)) { - Log.d(TAG, "Marking as played: $action") + Logd(TAG, "Marking as played: $action") feedItem.setPlayed(true) feedItem.media!!.setPosition(0) idRemove = feedItem.id - } else Log.d(TAG, "Setting position: $action") + } else Logd(TAG, "Setting position: $action") return Pair(idRemove, feedItem) } @UnstableApi @Synchronized fun processEpisodeActions(remoteActions: List) { - Log.d(TAG, "Processing " + remoteActions.size + " actions") + Logd(TAG, "Processing " + remoteActions.size + " actions") if (remoteActions.isEmpty()) return val playActionsToUpdate = getRemoteActionsOverridingLocalActions(remoteActions, synchronizationQueueStorage.queuedEpisodeActions) @@ -264,11 +265,11 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont } protected fun updateErrorNotification(exception: Exception) { - Log.d(TAG, "Posting sync error notification") + Logd(TAG, "Posting sync error notification") val description = ("${applicationContext.getString(R.string.gpodnetsync_error_descr)}${exception.message}") if (!gpodnetNotificationsEnabled()) { - Log.d(TAG, "Skipping sync error notification because of user setting") + Logd(TAG, "Skipping sync error notification because of user setting") return } if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) { diff --git a/app/src/main/java/ac/mdiq/podcini/net/sync/gpoddernet/GpodnetService.kt b/app/src/main/java/ac/mdiq/podcini/net/sync/gpoddernet/GpodnetService.kt index febf0ffa..74ec6d61 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/sync/gpoddernet/GpodnetService.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/sync/gpoddernet/GpodnetService.kt @@ -10,6 +10,7 @@ import ac.mdiq.podcini.net.sync.gpoddernet.model.GpodnetEpisodeActionPostRespons import ac.mdiq.podcini.net.sync.gpoddernet.model.GpodnetPodcast import ac.mdiq.podcini.net.sync.gpoddernet.model.GpodnetUploadChangesResponse import ac.mdiq.podcini.net.sync.model.* +import ac.mdiq.podcini.util.Logd import android.util.Log import okhttp3.* import okhttp3.Credentials.basic @@ -278,7 +279,7 @@ class GpodnetService(private val httpClient: OkHttpClient, baseHosturl: String?, @Throws(SyncServiceException::class) private fun uploadEpisodeActionsPartial(episodeActions: List?, from: Int, to: Int): UploadChangesResponse { try { - Log.d(TAG, "Uploading partial actions " + from + " to " + to + " of " + episodeActions!!.size) + Logd(TAG, "Uploading partial actions " + from + " to " + to + " of " + episodeActions!!.size) val url = URI(baseScheme, null, baseHost, basePort, String.format("/api/2/episodes/%s.json", username), null, null).toURL() @@ -424,7 +425,7 @@ class GpodnetService(private val httpClient: OkHttpClient, baseHosturl: String?, } else { if (BuildConfig.DEBUG) { try { - Log.d(TAG, response.body!!.string()) + Logd(TAG, response.body!!.string()) } catch (e: IOException) { e.printStackTrace() } diff --git a/app/src/main/java/ac/mdiq/podcini/net/sync/wifi/WifiSyncService.kt b/app/src/main/java/ac/mdiq/podcini/net/sync/wifi/WifiSyncService.kt index 51e1d717..bd643f06 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/sync/wifi/WifiSyncService.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/sync/wifi/WifiSyncService.kt @@ -15,6 +15,7 @@ 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.FeedItemUtil.hasAlmostEnded +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.SyncServiceEvent import android.content.Context import android.util.Log @@ -38,7 +39,7 @@ import kotlin.math.min var loginFail = false override fun doWork(): Result { - Log.d(TAG, "doWork() called") + Logd(TAG, "doWork() called") SynchronizationSettings.updateLastSynchronizationAttempt() setCurrentlyActive(true) @@ -107,7 +108,7 @@ import kotlin.math.min private var socket: Socket? = null @OptIn(UnstableApi::class) override fun login() { - Log.d(TAG, "serverIp: $hostIp serverPort: $hostPort $isGuest") + Logd(TAG, "serverIp: $hostIp serverPort: $hostPort $isGuest") EventBus.getDefault().post(SyncServiceEvent(R.string.sync_status_in_progress, "2")) if (!isPortInUse(hostPort)) { if (isGuest) { @@ -134,7 +135,7 @@ import kotlin.math.min try { socket = serverSocket!!.accept() while (true) { - Log.d(TAG, "waiting for guest message") + Logd(TAG, "waiting for guest message") try { receiveFromPeer() sendToPeer("Hello", "Hello, Client") @@ -191,30 +192,24 @@ import kotlin.math.min val messageData = parts[1] // Process the message based on the type when (messageType) { - "Hello" -> Log.d(TAG, "Received Hello message: $messageData") + "Hello" -> Logd(TAG, "Received Hello message: $messageData") "EpisodeActions" -> { val remoteActions = mutableListOf() val jsonArray = JSONArray(messageData) for (i in 0 until jsonArray.length()) { val jsonAction = jsonArray.getJSONObject(i) -// TODO: this conversion shouldn't be needed, check about the uploader -// val timeStr = jsonAction.getString("timestamp") -// val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US) -// val date = format.parse(timeStr) -// jsonAction.put("timestamp", date?.time?:0L) - - Log.d(TAG, "Received EpisodeActions message: $i $jsonAction") + Logd(TAG, "Received EpisodeActions message: $i $jsonAction") val action = readFromJsonObject(jsonAction) if (action != null) remoteActions.add(action) } processEpisodeActions(remoteActions) } "AllSent" -> { - Log.d(TAG, "Received AllSent message: $messageData") + Logd(TAG, "Received AllSent message: $messageData") return true } - else -> Log.d(TAG, "Received unknown message: $messageData") + else -> Logd(TAG, "Received unknown message: $messageData") } } } @@ -223,19 +218,19 @@ import kotlin.math.min @Throws(SyncServiceException::class) override fun getSubscriptionChanges(lastSync: Long): SubscriptionChanges? { - Log.d(TAG, "getSubscriptionChanges does nothing") + Logd(TAG, "getSubscriptionChanges does nothing") return null } @Throws(SyncServiceException::class) override fun uploadSubscriptionChanges(added: List, removed: List): UploadChangesResponse? { - Log.d(TAG, "uploadSubscriptionChanges does nothing") + Logd(TAG, "uploadSubscriptionChanges does nothing") return null } @Throws(SyncServiceException::class) override fun getEpisodeActionChanges(timestamp: Long): EpisodeActionChanges? { - Log.d(TAG, "getEpisodeActionChanges does nothing") + Logd(TAG, "getEpisodeActionChanges does nothing") return null } @@ -243,7 +238,7 @@ import kotlin.math.min var newTimeStamp = newTimeStamp_ EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_episodes_upload)) val queuedEpisodeActions: MutableList = synchronizationQueueStorage.queuedEpisodeActions - Log.d(TAG, "pushEpisodeActions queuedEpisodeActions: ${queuedEpisodeActions.size}") + Logd(TAG, "pushEpisodeActions queuedEpisodeActions: ${queuedEpisodeActions.size}") if (lastSync == 0L) { EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_upload_played)) @@ -253,7 +248,7 @@ import kotlin.math.min val comItems = mutableSetOf() comItems.addAll(pausedItems) comItems.addAll(readItems) - Log.d(TAG, "First sync. Upload state for all " + comItems.size + " played episodes") + Logd(TAG, "First sync. Upload state for all " + comItems.size + " played episodes") for (item in comItems) { val media = item.media ?: continue val played = EpisodeAction.Builder(item, EpisodeAction.PLAY) @@ -268,10 +263,10 @@ import kotlin.math.min if (queuedEpisodeActions.isNotEmpty()) { LockingAsyncExecutor.lock.lock() try { - Log.d(TAG, "Uploading ${queuedEpisodeActions.size} actions: ${StringUtils.join(queuedEpisodeActions, ", ")}") + Logd(TAG, "Uploading ${queuedEpisodeActions.size} actions: ${StringUtils.join(queuedEpisodeActions, ", ")}") val postResponse = uploadEpisodeActions(queuedEpisodeActions) newTimeStamp = postResponse.timestamp - Log.d(TAG, "Upload episode response: $postResponse") + Logd(TAG, "Upload episode response: $postResponse") synchronizationQueueStorage.clearEpisodeActionQueue() } finally { LockingAsyncExecutor.lock.unlock() @@ -301,7 +296,7 @@ import kotlin.math.min val episodeAction = queuedEpisodeActions[i] val obj = episodeAction.writeToJsonObject() if (obj != null) { - Log.d(TAG, "sending EpisodeAction: $obj") + Logd(TAG, "sending EpisodeAction: $obj") list.put(obj) } } @@ -325,18 +320,18 @@ import kotlin.math.min } feedItem.media = getFeedMedia(feedItem.media!!.id) var idRemove = 0L - Log.d(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}") + Logd(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}") if (feedItem.media!!.getLastPlayedTime() < (action.timestamp?.time?:0L)) { feedItem.media!!.setPosition(action.position * 1000) feedItem.media!!.setLastPlayedTime(action.timestamp!!.time) if (hasAlmostEnded(feedItem.media!!)) { - Log.d(TAG, "Marking as played") + Logd(TAG, "Marking as played") feedItem.setPlayed(true) feedItem.media!!.setPosition(0) idRemove = feedItem.id - } else Log.d(TAG, "Setting position") + } else Logd(TAG, "Setting position") persistFeedMediaPlaybackInfo(feedItem.media) - } else Log.d(TAG, "local is newer, no change") + } else Logd(TAG, "local is newer, no change") return Pair(idRemove, feedItem) } diff --git a/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt b/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt index a151a2de..a5e5cd48 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt @@ -40,7 +40,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) { private var initialized = false private var eventsRegistered = false - private var loadedFeedMedia: Long = -1 + private var loadedFeedMediaId: Long = -1 val position: Int get() = playbackService?.currentPosition ?: getMedia()?.getPosition() ?: Playable.INVALID_TIME @@ -114,7 +114,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) { // ignore } unbind() - media = null +// media = null released = true if (eventsRegistered) { @@ -235,8 +235,9 @@ abstract class PlaybackController(private val activity: FragmentActivity) { } private fun checkMediaInfoLoaded() { - if (!mediaInfoLoaded || loadedFeedMedia != PlaybackPreferences.currentlyPlayingFeedMediaId) { - loadedFeedMedia = PlaybackPreferences.currentlyPlayingFeedMediaId + if (!mediaInfoLoaded || loadedFeedMediaId != PlaybackPreferences.currentlyPlayingFeedMediaId) { + loadedFeedMediaId = PlaybackPreferences.currentlyPlayingFeedMediaId + Logd(TAG, "checkMediaInfoLoaded: $loadedFeedMediaId") loadMediaInfo() } mediaInfoLoaded = true 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 b0503545..6607f3af 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 @@ -48,12 +48,11 @@ import androidx.media3.extractor.DefaultExtractorsFactory import androidx.media3.extractor.mp3.Mp3Extractor import androidx.media3.ui.DefaultTrackNameProvider import androidx.media3.ui.TrackNameProvider -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import java.io.File import java.io.IOException +import java.lang.Runnable import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean @@ -83,8 +82,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP private var isShutDown = false private var seekLatch: CountDownLatch? = null - private val bufferUpdateInterval = 5L - private val bufferingUpdateDisposable: Disposable + private val bufferUpdateInterval = 5000L +// private val bufferingUpdateDisposable: Disposable private var mediaSource: MediaSource? = null private var playbackParameters: PlaybackParameters @@ -125,7 +124,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP } private fun release() { - bufferingUpdateDisposable.dispose() +// bufferingUpdateDisposable.dispose() // exoplayerListener = null exoPlayer?.stop() @@ -670,11 +669,20 @@ 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) +// bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe { +// bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage) +// } + val scope = CoroutineScope(Dispatchers.Main) + scope.launch { + while (true) { + delay(bufferUpdateInterval) + withContext(Dispatchers.Main) { + bufferingUpdateListener?.accept(exoPlayer!!.bufferedPercentage) + } } + } } override fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean) { 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 10e99de3..eb57e70b 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 @@ -4,8 +4,8 @@ import ac.mdiq.podcini.R import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink import ac.mdiq.podcini.playback.PlaybackServiceStarter import ac.mdiq.podcini.playback.base.MediaPlayerBase -import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerCallback +import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.cast.CastPsmp import ac.mdiq.podcini.playback.cast.CastStateListener @@ -91,10 +91,10 @@ import androidx.media3.session.SessionResult import androidx.work.impl.utils.futures.SettableFuture import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture -import io.reactivex.Observable -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 org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -108,7 +108,8 @@ import kotlin.math.max @UnstableApi class PlaybackService : MediaSessionService() { private var mediaPlayer: MediaPlayerBase? = null - private var positionEventTimer: Disposable? = null + + val scope = CoroutineScope(Dispatchers.Main) private lateinit var customMediaNotificationProvider: CustomMediaNotificationProvider private val notificationCustomButtons = NotificationCustomButton.entries.map { command -> command.commandButton } @@ -261,7 +262,6 @@ class PlaybackService : MediaSessionService() { currentMediaType = MediaType.UNKNOWN castStateListener.destroy() -// cancelPositionObserver() LocalMediaPlayer.cleanup() mediaSession?.run { player.release() @@ -367,139 +367,6 @@ class PlaybackService : MediaSessionService() { return mediaSession } -// private fun loadQueueForMediaSession() { -// Single.create { emitter: SingleEmitter?> -> -// val queueItems: MutableList = ArrayList() -// for (feedItem in DBReader.getQueue()) { -// if (feedItem.media != null) { -// val mediaDescription = feedItem.media!!.mediaItem.description -// queueItems.add(MediaSessionCompat.QueueItem(mediaDescription, feedItem.id)) -// } -// } -// emitter.onSuccess(queueItems) -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe({ -//// mediaSession?.setQueue(queueItems) -// }, -// { obj: Throwable -> obj.printStackTrace() }) -// } - -// private fun createBrowsableMediaItem(@StringRes title: Int, @DrawableRes icon: Int, numEpisodes: Int): MediaItem { -// val uri = Uri.Builder() -// .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) -// .authority(resources.getResourcePackageName(icon)) -// .appendPath(resources.getResourceTypeName(icon)) -// .appendPath(resources.getResourceEntryName(icon)) -// .build() -// -// val description = MediaDescription.Builder() -// .setIconUri(uri) -// .setMediaId(resources.getString(title)) -// .setTitle(resources.getString(title)) -// .setSubtitle(resources.getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes)) -// .build() -// return MediaItem(description, MediaItem.FLAG_BROWSABLE) -// } - -// private fun createBrowsableMediaItemForFeed(feed: Feed): MediaItem { -// val builder = MediaDescription.Builder() -// .setMediaId("FeedId:" + feed.id) -// .setTitle(feed.title) -// .setDescription(feed.description) -// .setSubtitle(feed.getCustomTitle()) -// if (feed.imageUrl != null) { -// builder.setIconUri(Uri.parse(feed.imageUrl)) -// } -// if (feed.link != null) { -// builder.setMediaUri(Uri.parse(feed.link)) -// } -// val description = builder.build() -// return MediaItem(description, MediaItem.FLAG_BROWSABLE) -// } - -// override fun onLoadChildren(parentId: String, result: Result>) { -// Log.d(TAG, "OnLoadChildren: parentMediaId=$parentId") -// result.detach() -// -// Completable.create { emitter: CompletableEmitter -> -// result.sendResult(loadChildrenSynchronous(parentId)) -// emitter.onComplete() -// } -// .subscribeOn(Schedulers.io()) -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe( -// {}, { e: Throwable -> -// e.printStackTrace() -// result.sendResult(null) -// }) -// } - -// private fun loadChildrenSynchronous(parentId: String): List? { -// val mediaItems: MutableList = ArrayList() -// if (parentId == resources.getString(R.string.app_name)) { -// val currentlyPlaying = currentPlayerStatus.toLong() -// if (currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PLAYING.toLong() -// || currentlyPlaying == PlaybackPreferences.PLAYER_STATUS_PAUSED.toLong()) { -// mediaItems.add(createBrowsableMediaItem(R.string.current_playing_episode, R.drawable.ic_play_48dp, 1)) -// } -// mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_play_black, -// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.QUEUED)))) -// mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black, -// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED)))) -// mediaItems.add(createBrowsableMediaItem(R.string.episodes_label, R.drawable.ic_feed_black, -// DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.UNPLAYED)))) -// val feeds = DBReader.getFeedList() -// for (feed in feeds) { -// mediaItems.add(createBrowsableMediaItemForFeed(feed)) -// } -// return mediaItems -// } -// -// val feedItems: List -// when { -// parentId == resources.getString(R.string.queue_label) -> { -// feedItems = DBReader.getQueue() -// } -// parentId == resources.getString(R.string.downloads_label) -> { -// feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, -// FeedItemFilter(FeedItemFilter.DOWNLOADED), downloadsSortedOrder) -// } -// parentId == resources.getString(R.string.episodes_label) -> { -// feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, -// FeedItemFilter(FeedItemFilter.UNPLAYED), allEpisodesSortOrder) -// } -// parentId.startsWith("FeedId:") -> { -// val feedId = parentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].toLong() -// val feed = DBReader.getFeed(feedId) -// feedItems = if (feed != null) DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed.sortOrder) else listOf() -// } -// parentId == getString(R.string.current_playing_episode) -> { -// val playable = createInstanceFromPreferences(this) -// if (playable is FeedMedia) { -// feedItems = listOf(playable.item) -// } else { -// return null -// } -// } -// else -> { -// Log.e(TAG, "Parent ID not found: $parentId") -// return null -// } -// } -// var count = 0 -// for (feedItem in feedItems) { -// if (feedItem?.media != null) { -// mediaItems.add(feedItem.media!!.mediaItem) -// if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) { -// break -// } -// } -// } -// return mediaItems -// } - override fun onBind(intent: Intent?): IBinder? { Logd(TAG, "Received onBind event") return if (intent?.action != null && TextUtils.equals(intent.action, SERVICE_INTERFACE)) { @@ -544,23 +411,37 @@ class PlaybackService : MediaSessionService() { val allowStreamAlways = intent.getBooleanExtra(PlaybackServiceConstants.EXTRA_ALLOW_STREAM_ALWAYS, false) sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0) if (allowStreamAlways) isAllowMobileStreaming = true - Observable.fromCallable { - if (playable is FeedMedia) return@fromCallable DBReader.getFeedMedia(playable.id) - else return@fromCallable playable + +// Observable.fromCallable { +// if (playable is FeedMedia) return@fromCallable DBReader.getFeedMedia(playable.id) +// else return@fromCallable playable +// } +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe( +// { loadedPlayable: Playable? -> startPlaying(loadedPlayable, allowStreamThisTime) }, +// { error: Throwable -> +// Logd(TAG, "Playable was not found. Stopping service.") +// error.printStackTrace() +// }) + + scope.launch { + try { + val loadedPlayable = withContext(Dispatchers.IO) { + if (playable is FeedMedia) DBReader.getFeedMedia(playable.id) + else playable + } + withContext(Dispatchers.Main) { + startPlaying(loadedPlayable, allowStreamThisTime) + } + } catch (e: Throwable) { + Logd(TAG, "Playable was not found. Stopping service.") + e.printStackTrace() + } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { loadedPlayable: Playable? -> startPlaying(loadedPlayable, allowStreamThisTime) }, - { error: Throwable -> - Logd(TAG, "Playable was not found. Stopping service.") - error.printStackTrace() - }) return START_NOT_STICKY } - else -> { -// mediaSession?.controller?.transportControls?.sendCustomAction(customAction, null) - } + else -> {} } } @@ -724,15 +605,28 @@ 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() - }) +// 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) { + createInstanceFromPreferences(applicationContext) + } + withContext(Dispatchers.Main) { + startPlaying(playable, false) + } + } catch (e: Throwable) { + Logd(TAG, "Playable was not loaded from preferences. Stopping service.") + e.printStackTrace() + } + } } private fun startPlaying(playable: Playable?, allowStreamThisTime: Boolean) { @@ -750,7 +644,6 @@ class PlaybackService : MediaSessionService() { mediaPlayer?.playMediaObject(playable, stream, startWhenPrepared = true, true) recreateMediaSessionIfNeeded() -// updateNotificationAndMediaSession(playable) addPlayableToQueue(playable) // EventBus.getDefault().post(PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_RESTARTED)) } @@ -767,10 +660,8 @@ class PlaybackService : MediaSessionService() { fun notifyVideoSurfaceAbandoned() { mediaPlayer?.pause(abandonFocus = true, reinit = false) mediaPlayer?.resetVideoSurface() -// updateNotificationAndMediaSession(playable) } - // TODO: positionEventTimer also monitors position, should be combined? private val taskManagerCallback: PSTMCallback = object : PSTMCallback { override fun positionSaverTick() { if (currentPosition != previousPosition) { @@ -789,7 +680,6 @@ class PlaybackService : MediaSessionService() { override fun onChapterLoaded(media: Playable?) { sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0) -// updateMediaSession(MediaPlayerBase.status) } } @@ -797,29 +687,24 @@ class PlaybackService : MediaSessionService() { override fun statusChanged(newInfo: MediaPlayerInfo?) { currentMediaType = mediaPlayer?.getCurrentMediaType() ?: MediaType.UNKNOWN Logd(TAG, "statusChanged called ${newInfo?.playerStatus}") -// updateMediaSession(newInfo?.playerStatus) if (newInfo != null) { when (newInfo.playerStatus) { - PlayerStatus.INITIALIZED -> { + PlayerStatus.INITIALIZED -> if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.playerInfo.playable, mediaPlayer!!.playerInfo.playerStatus, currentitem) -// updateNotificationAndMediaSession(newInfo.playable) - } PlayerStatus.PREPARED -> { if (mediaPlayer != null) writeMediaPlaying(mediaPlayer!!.playerInfo.playable, mediaPlayer!!.playerInfo.playerStatus, currentitem) taskManager.startChapterLoader(newInfo.playable!!) } PlayerStatus.PAUSED -> { -// updateNotificationAndMediaSession(newInfo.playable) -// cancelPositionObserver() if (mediaPlayer != null) writePlayerStatus(MediaPlayerBase.status) } PlayerStatus.STOPPED -> {} PlayerStatus.PLAYING -> { - if (mediaPlayer != null) writePlayerStatus(MediaPlayerBase.status) + if (mediaPlayer != null) { + writePlayerStatus(MediaPlayerBase.status) + } saveCurrentPosition(true, null, Playable.INVALID_TIME) recreateMediaSessionIfNeeded() -// updateNotificationAndMediaSession(newInfo.playable) -// setupPositionObserver() // set sleep timer if auto-enabled var autoEnableByTime = true val fromSetting = autoEnableFrom() @@ -857,7 +742,6 @@ class PlaybackService : MediaSessionService() { override fun onMediaChanged(reloadUI: Boolean) { Logd(TAG, "reloadUI callback reached") if (reloadUI) sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_RELOAD, 0) -// updateNotificationAndMediaSession(this@PlaybackService.playable) } override fun onPostPlayback(media: Playable?, ended: Boolean, skipped: Boolean, playingNext: Boolean) { @@ -874,7 +758,6 @@ class PlaybackService : MediaSessionService() { override fun onPlaybackPause(playable: Playable?, position: Int) { taskManager.cancelPositionSaver() -// cancelPositionObserver() saveCurrentPosition(position == Playable.INVALID_TIME || playable == null, playable, position) taskManager.cancelWidgetUpdater() if (playable != null) { @@ -917,7 +800,6 @@ class PlaybackService : MediaSessionService() { // Playable is being streamed and does not have a duration specified in the feed playable.setDuration(mediaPlayer!!.getDuration()) DBWriter.persistFeedMedia(playable as FeedMedia) -// updateNotificationAndMediaSession(playable) } } } @@ -942,7 +824,7 @@ class PlaybackService : MediaSessionService() { } private fun getNextInQueue(currentMedia: Playable?): Playable? { - Logd(TAG, "getNextInQueue currentMedia: ${currentMedia?.getEpisodeTitle()}") + Logd(TAG, "*** expensive call getNextInQueue currentMedia: ${currentMedia?.getEpisodeTitle()}") if (currentMedia !is FeedMedia) { Logd(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding") writeNoMediaPlaying() @@ -966,7 +848,6 @@ class PlaybackService : MediaSessionService() { if (!isFollowQueue) { Logd(TAG, "getNextInQueue(), but follow queue is not enabled.") writeMediaPlaying(nextItem.media, PlayerStatus.STOPPED, currentitem) -// updateNotificationAndMediaSession(nextItem.media) return null } @@ -975,6 +856,7 @@ class PlaybackService : MediaSessionService() { writeNoMediaPlaying() return null } + EventBus.getDefault().post(StartPlayEvent(nextItem)) return nextItem.media } @@ -986,7 +868,6 @@ class PlaybackService : MediaSessionService() { clearCurrentlyPlayingTemporaryPlaybackSpeed() if (stopPlaying) { taskManager.cancelPositionSaver() -// cancelPositionObserver() } if (mediaType == null) { sendNotificationBroadcast(PlaybackServiceConstants.NOTIFICATION_TYPE_PLAYBACK_END, 0) @@ -1070,7 +951,6 @@ class PlaybackService : MediaSessionService() { DBWriter.deleteFeedMediaOfItem(this@PlaybackService, media.id) Logd(TAG, "Episode Deleted") } -// notifyChildrenChanged(getString(R.string.queue_label)) } } @@ -1119,76 +999,6 @@ class PlaybackService : MediaSessionService() { } } - /** - * Updates the Media Session for the corresponding status. - */ -// private fun updateMediaSession(playerStatus: PlayerStatus?) { -// val sessionState = PlaybackStateCompat.Builder() -// val state = if (playerStatus != null) { -// when (playerStatus) { -// PlayerStatus.PLAYING -> PlaybackStateCompat.STATE_PLAYING -// PlayerStatus.FALLBACK -> PlaybackStateCompat.STATE_PLAYING -// PlayerStatus.PREPARED, PlayerStatus.PAUSED -> PlaybackStateCompat.STATE_PAUSED -// PlayerStatus.STOPPED -> PlaybackStateCompat.STATE_STOPPED -// PlayerStatus.SEEKING -> PlaybackStateCompat.STATE_FAST_FORWARDING -// PlayerStatus.PREPARING, PlayerStatus.INITIALIZING -> PlaybackStateCompat.STATE_CONNECTING -// PlayerStatus.ERROR -> PlaybackStateCompat.STATE_ERROR -// PlayerStatus.INITIALIZED, PlayerStatus.INDETERMINATE -> PlaybackStateCompat.STATE_NONE -// } -// } else { -// PlaybackStateCompat.STATE_NONE -// } -// -// sessionState.setState(state, currentPosition.toLong(), currentPlaybackSpeed) -// val capabilities = (PlaybackStateCompat.ACTION_PLAY -// or PlaybackStateCompat.ACTION_PLAY_PAUSE -// or PlaybackStateCompat.ACTION_REWIND -// or PlaybackStateCompat.ACTION_PAUSE -// or PlaybackStateCompat.ACTION_FAST_FORWARD -// or PlaybackStateCompat.ACTION_SEEK_TO -// or PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED) -// -// sessionState.setActions(capabilities) - - // On Android Auto, custom actions are added in the following order around the play button, if no default - // actions are present: Near left, near right, far left, far right, additional actions panel -// val rewindBuilder = PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_REWIND, getString(R.string.rewind_label), R.drawable.ic_notification_fast_rewind) -// WearMediaSession.addWearExtrasToAction(rewindBuilder) -//// sessionState.addCustomAction(rewindBuilder.build()) - -// val fastForwardBuilder = PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_FAST_FORWARD, getString(R.string.fast_forward_label), R.drawable.ic_notification_fast_forward) -// WearMediaSession.addWearExtrasToAction(fastForwardBuilder) -// sessionState.addCustomAction(fastForwardBuilder.build()) - -// if (showPlaybackSpeedOnFullNotification()) -// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_CHANGE_PLAYBACK_SPEED, -// getString(R.string.playback_speed), R.drawable.ic_notification_playback_speed).build()) - -// if (showNextChapterOnFullNotification()) { -// if (!playable?.getChapters().isNullOrEmpty()) -// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_NEXT_CHAPTER, -// getString(R.string.next_chapter), R.drawable.ic_notification_next_chapter).build()) -// } - -// if (showSkipOnFullNotification()) -// sessionState.addCustomAction(PlaybackStateCompat.CustomAction.Builder(CUSTOM_ACTION_SKIP_TO_NEXT, -// getString(R.string.skip_episode_label), R.drawable.ic_notification_skip).build()) - - -// if (mediaSession != null) { -// WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!) -//// mediaSession!!.setPlaybackState(sessionState.build()) -// } -// } - -// private fun updateMediaSessionMetadata(p: Playable?) { -// if (p == null || mediaSession == null) return -// -// // TODO: what's this? -// mediaSession!!.setSessionActivity( -// PendingIntent.getActivity(this, R.id.pending_intent_player_activity, getPlayerActivityIntent(this), FLAG_IMMUTABLE)) -// } - /** * Persists the current position and last played time of the media file. * @@ -1485,30 +1295,10 @@ class PlaybackService : MediaSessionService() { mediaPlayer?.setAudioTrack(track) } -// private fun setupPositionObserver() { -// positionEventTimer?.dispose() -// -// Log.d(TAG, "Setting up position observer") -// positionEventTimer = Observable.interval(POSITION_EVENT_INTERVAL, TimeUnit.SECONDS) -// .observeOn(AndroidSchedulers.mainThread()) -//// .takeWhile { currentPosition != previousPosition } -// .subscribe { -// Log.d(TAG, "positionEventTimer currentPosition: $currentPosition, currentPlaybackSpeed: $currentPlaybackSpeed") -// EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, duration)) -// previousPosition = currentPosition -// skipEndingIfNecessary() -// } -// } -// -// private fun cancelPositionObserver() { -// positionEventTimer?.dispose() -// } - private fun addPlayableToQueue(playable: Playable?) { if (playable is FeedMedia) { val itemId = playable.item?.id ?: return DBWriter.addQueueItem(this, false, true, itemId) -// notifyChildrenChanged(getString(R.string.queue_label)) } } 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 7196cd6f..561d849e 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 @@ -1,26 +1,25 @@ package ac.mdiq.podcini.playback.service +import ac.mdiq.podcini.preferences.SleepTimerPreferences +import ac.mdiq.podcini.storage.model.playback.Playable +import ac.mdiq.podcini.ui.widget.WidgetUpdater +import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState +import ac.mdiq.podcini.util.ChapterUtils +import ac.mdiq.podcini.util.Logd +import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent import android.content.Context import android.os.Handler import android.os.Looper import android.os.Vibrator import android.util.Log -import ac.mdiq.podcini.preferences.SleepTimerPreferences -import ac.mdiq.podcini.util.ChapterUtils -import ac.mdiq.podcini.ui.widget.WidgetUpdater -import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState -import ac.mdiq.podcini.util.event.playback.SleepTimerUpdatedEvent -import ac.mdiq.podcini.storage.model.playback.Playable -import io.reactivex.Completable -import io.reactivex.CompletableEmitter -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 org.greenrobot.eventbus.EventBus import java.util.concurrent.ScheduledFuture import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.TimeUnit -import kotlin.concurrent.Volatile /** * Manages the background tasks of PlaybackSerivce, i.e. @@ -38,11 +37,40 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb private var widgetUpdaterFuture: ScheduledFuture<*>? = null private var sleepTimerFuture: ScheduledFuture<*>? = null - @Volatile - private var chapterLoaderFuture: Disposable? = null +// @Volatile +// private var chapterLoaderFuture: Disposable? = null private var sleepTimer: SleepTimer? = null + /** + * Returns true if the sleep timer is currently active. + */ + @get:Synchronized + val isSleepTimerActive: Boolean + get() = (sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture!!.isCancelled + && !sleepTimerFuture!!.isDone) && sleepTimer!!.getWaitingTime() > 0 + + /** + * Returns the current sleep timer time or 0 if the sleep timer is not active. + */ + @get:Synchronized + val sleepTimerTimeLeft: Long + get() = if (isSleepTimerActive) sleepTimer!!.getWaitingTime() else 0 + + /** + * Returns true if the widget updater is currently running. + */ + @get:Synchronized + val isWidgetUpdaterActive: Boolean + get() = widgetUpdaterFuture != null && !widgetUpdaterFuture!!.isCancelled && !widgetUpdaterFuture!!.isDone + + /** + * Returns true if the position saver is currently running. + */ + @get:Synchronized + val isPositionSaverActive: Boolean + get() = positionSaverFuture != null && !positionSaverFuture!!.isCancelled && !positionSaverFuture!!.isDone + /** * Sets up a new PSTM. This method will also start the queue loader task. * @@ -67,17 +95,10 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb positionSaver = useMainThreadIfNecessary(positionSaver) positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, POSITION_SAVER_WAITING_INTERVAL.toLong(), POSITION_SAVER_WAITING_INTERVAL.toLong(), TimeUnit.MILLISECONDS) - Log.d(TAG, "Started PositionSaver") - } else Log.d(TAG, "Call to startPositionSaver was ignored.") + Logd(TAG, "Started PositionSaver") + } else Logd(TAG, "Call to startPositionSaver was ignored.") } - @get:Synchronized - val isPositionSaverActive: Boolean - /** - * Returns true if the position saver is currently running. - */ - get() = positionSaverFuture != null && !positionSaverFuture!!.isCancelled && !positionSaverFuture!!.isDone - /** * Cancels the position saver. If the position saver is not running, nothing will happen. */ @@ -85,7 +106,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb fun cancelPositionSaver() { if (isPositionSaverActive) { positionSaverFuture!!.cancel(false) - Log.d(TAG, "Cancelled PositionSaver") + Logd(TAG, "Cancelled PositionSaver") } } @@ -99,8 +120,8 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb widgetUpdater = useMainThreadIfNecessary(widgetUpdater) widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL.toLong(), WIDGET_UPDATER_NOTIFICATION_INTERVAL.toLong(), TimeUnit.MILLISECONDS) - Log.d(TAG, "Started WidgetUpdater") - } else Log.d(TAG, "Call to startWidgetUpdater was ignored.") + Logd(TAG, "Started WidgetUpdater") + } } /** @@ -110,7 +131,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb fun requestWidgetUpdate() { val state = callback.requestWidgetState() if (!schedExecutor.isShutdown) schedExecutor.execute { WidgetUpdater.updateWidget(context, state) } - else Log.d(TAG, "Call to requestWidgetUpdate was ignored.") + else Logd(TAG, "Call to requestWidgetUpdate was ignored.") } /** @@ -124,28 +145,20 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb fun setSleepTimer(waitingTime: Long) { require(waitingTime > 0) { "Waiting time <= 0" } - Log.d(TAG, "Setting sleep timer to $waitingTime milliseconds") + Logd(TAG, "Setting sleep timer to $waitingTime milliseconds") if (isSleepTimerActive) sleepTimerFuture!!.cancel(true) sleepTimer = SleepTimer(waitingTime) sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS) EventBus.getDefault().post(SleepTimerUpdatedEvent.justEnabled(waitingTime)) } - @get:Synchronized - val isSleepTimerActive: Boolean - /** - * Returns true if the sleep timer is currently active. - */ - get() = (sleepTimer != null && sleepTimerFuture != null && !sleepTimerFuture!!.isCancelled - && !sleepTimerFuture!!.isDone) && sleepTimer!!.getWaitingTime() > 0 - /** * Disables the sleep timer. If the sleep timer is not active, nothing will happen. */ @Synchronized fun disableSleepTimer() { if (isSleepTimerActive) { - Log.d(TAG, "Disabling sleep timer") + Logd(TAG, "Disabling sleep timer") sleepTimer!!.cancel() } } @@ -156,25 +169,11 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb @Synchronized fun restartSleepTimer() { if (isSleepTimerActive) { - Log.d(TAG, "Restarting sleep timer") + Logd(TAG, "Restarting sleep timer") sleepTimer!!.restart() } } - @get:Synchronized - val sleepTimerTimeLeft: Long - /** - * Returns the current sleep timer time or 0 if the sleep timer is not active. - */ - get() = if (isSleepTimerActive) sleepTimer!!.getWaitingTime() else 0 - - @get:Synchronized - val isWidgetUpdaterActive: Boolean - /** - * Returns true if the widget updater is currently running. - */ - get() = widgetUpdaterFuture != null && !widgetUpdaterFuture!!.isCancelled && !widgetUpdaterFuture!!.isDone - /** * Cancels the widget updater. If the widget updater is not running, nothing will happen. */ @@ -182,7 +181,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb fun cancelWidgetUpdater() { if (isWidgetUpdaterActive) { widgetUpdaterFuture!!.cancel(false) - Log.d(TAG, "Cancelled WidgetUpdater") + Logd(TAG, "Cancelled WidgetUpdater") } } @@ -193,24 +192,35 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb */ @Synchronized fun startChapterLoader(media: Playable) { - chapterLoaderFuture?.dispose() - chapterLoaderFuture = null +// chapterLoaderFuture?.dispose() +// chapterLoaderFuture = null if (!media.chaptersLoaded()) { - chapterLoaderFuture = Completable.create { emitter: CompletableEmitter -> - ChapterUtils.loadChapters(media, context, false) - emitter.onComplete() +// 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 { + ChapterUtils.loadChapters(media, context, false) + withContext(Dispatchers.Main) { + callback.onChapterLoaded(media) + } + } catch (e: Throwable) { + Log.d(TAG, "Error loading chapters: ${Log.getStackTraceString(e)}") + } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ callback.onChapterLoaded(media) }, - { throwable: Throwable? -> - Log.d(TAG, "Error loading chapters: " + Log.getStackTraceString(throwable)) - }) } } - /** * Cancels all tasks. The PSTM will be in the initial state after execution of this method. */ @@ -220,8 +230,8 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb cancelWidgetUpdater() disableSleepTimer() - chapterLoaderFuture?.dispose() - chapterLoaderFuture = null +// chapterLoaderFuture?.dispose() +// chapterLoaderFuture = null } /** @@ -251,14 +261,14 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb private var shakeListener: ShakeListener? = null override fun run() { - Log.d(TAG, "Starting") + Logd(TAG, "Starting SleepTimer") var lastTick = System.currentTimeMillis() EventBus.getDefault().post(SleepTimerUpdatedEvent.updated(timeLeft)) while (timeLeft > 0) { try { Thread.sleep(UPDATE_INTERVAL) } catch (e: InterruptedException) { - Log.d(TAG, "Thread was interrupted while waiting") + Logd(TAG, "Thread was interrupted while waiting") e.printStackTrace() break } @@ -269,7 +279,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb EventBus.getDefault().post(SleepTimerUpdatedEvent.updated(timeLeft)) if (timeLeft < NOTIFICATION_THRESHOLD) { - Log.d(TAG, "Sleep timer is about to expire") + Logd(TAG, "Sleep timer is about to expire") if (SleepTimerPreferences.vibrate() && !hasVibrated) { val v = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator if (v != null) { @@ -280,7 +290,7 @@ class PlaybackServiceTaskManager(private val context: Context, private val callb if (shakeListener == null && SleepTimerPreferences.shakeToReset()) shakeListener = ShakeListener(context, this) } if (timeLeft <= 0) { - Log.d(TAG, "Sleep timer expired") + Logd(TAG, "Sleep timer expired") shakeListener?.pause() shakeListener = null diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/QuickSettingsTileService.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/QuickSettingsTileService.kt index 6ddf4569..a7cf44d2 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/service/QuickSettingsTileService.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/service/QuickSettingsTileService.kt @@ -12,6 +12,7 @@ import androidx.annotation.RequiresApi import androidx.media3.common.util.UnstableApi import ac.mdiq.podcini.preferences.PlaybackPreferences import ac.mdiq.podcini.receiver.MediaButtonReceiver +import ac.mdiq.podcini.util.Logd @UnstableApi @RequiresApi(api = Build.VERSION_CODES.N) @@ -43,7 +44,7 @@ class QuickSettingsTileService : TileService() { fun updateTile() { val qsTile = qsTile - if (qsTile == null) Log.d(TAG, "Ignored call to update QS tile: getQsTile() returned null.") + if (qsTile == null) Logd(TAG, "Ignored call to update QS tile: getQsTile() returned null.") else { val isPlaying = (PlaybackService.isRunning && PlaybackPreferences.currentPlayerStatus == PlaybackPreferences.PLAYER_STATUS_PLAYING) qsTile.state = if (isPlaying) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE diff --git a/app/src/main/java/ac/mdiq/podcini/playback/service/ShakeListener.kt b/app/src/main/java/ac/mdiq/podcini/playback/service/ShakeListener.kt index 45686e5a..07db4ca8 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/service/ShakeListener.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/service/ShakeListener.kt @@ -7,6 +7,7 @@ import android.hardware.SensorEventListener import android.hardware.SensorManager import android.util.Log import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.SleepTimer +import ac.mdiq.podcini.util.Logd import kotlin.math.sqrt internal class ShakeListener(private val mContext: Context, private val mSleepTimer: SleepTimer) : SensorEventListener { @@ -42,7 +43,7 @@ internal class ShakeListener(private val mContext: Context, private val mSleepTi val gForce = sqrt((gX * gX + gY * gY + gZ * gZ).toDouble()) if (gForce > 2.25) { - Log.d(TAG, "Detected shake $gForce") + Logd(TAG, "Detected shake $gForce") mSleepTimer.restart() } } diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt index 9f0735d6..7af03f16 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedPreferences import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.Playable +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.PlayerStatusEvent import android.content.Context import android.content.SharedPreferences @@ -133,7 +134,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen @JvmStatic fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus, item: FeedItem? = null) { - Log.d(TAG, "Writing playback preferences") + Logd(TAG, "Writing playback preferences ${playable?.getIdentifier()}") val editor = prefs.edit() if (playable == null) { @@ -159,7 +160,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen @JvmStatic fun writePlayerStatus(playerStatus: PlayerStatus) { - Log.d(TAG, "Writing player status playback preferences") + Logd(TAG, "Writing player status playback preferences") val editor = prefs.edit() editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus)) @@ -191,7 +192,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen @JvmStatic fun createInstanceFromPreferences(context: Context): Playable? { val currentlyPlayingMedia = currentlyPlayingMediaType - Log.d(TAG, "currentlyPlayingMedia: $currentlyPlayingMedia") + Logd(TAG, "currentlyPlayingMedia: $currentlyPlayingMedia") if (currentlyPlayingMedia != NO_MEDIA_PLAYING) { val prefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) return createInstanceFromPreferences(currentlyPlayingMedia.toInt(), prefs) diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/SleepTimerPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/SleepTimerPreferences.kt index bb1024de..107cadb8 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/SleepTimerPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/SleepTimerPreferences.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.preferences +import ac.mdiq.podcini.util.Logd import android.content.Context import android.content.SharedPreferences import android.util.Log @@ -30,7 +31,7 @@ object SleepTimerPreferences { */ @JvmStatic fun init(context: Context) { - Log.d(TAG, "Creating new instance of SleepTimerPreferences") + Logd(TAG, "Creating new instance of SleepTimerPreferences") prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) } diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt index 1a66a696..925979c5 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -6,6 +6,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedPreferences.NewEpisodesAction import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.storage.model.feed.SubscriptionsFilter import ac.mdiq.podcini.storage.model.playback.MediaType +import ac.mdiq.podcini.util.Logd import android.content.Context import android.content.SharedPreferences import android.os.Build @@ -145,7 +146,7 @@ object UserPreferences { */ @JvmStatic fun init(context: Context) { - Log.d(TAG, "Creating new instance of UserPreferences") + Logd(TAG, "Creating new instance of UserPreferences") UserPreferences.context = context.applicationContext prefs = PreferenceManager.getDefaultSharedPreferences(context) @@ -697,11 +698,11 @@ object UserPreferences { fun getDataFolder(type: String?): File? { var dataFolder = getTypeDir(prefs.getString(PREF_DATA_FOLDER, null), type) if (dataFolder == null || !dataFolder.canWrite()) { - Log.d(TAG, "User data folder not writable or not set. Trying default.") + Logd(TAG, "User data folder not writable or not set. Trying default.") dataFolder = context.getExternalFilesDir(type) } if (dataFolder == null || !dataFolder.canWrite()) { - Log.d(TAG, "Default data folder not available or not writable. Falling back to internal memory.") + Logd(TAG, "Default data folder not available or not writable. Falling back to internal memory.") dataFolder = getTypeDir(context.filesDir.absolutePath, type) } return dataFolder @@ -727,7 +728,7 @@ object UserPreferences { @JvmStatic fun setDataFolder(dir: String) { - Log.d(TAG, "setDataFolder(dir: $dir)") + Logd(TAG, "setDataFolder(dir: $dir)") prefs.edit().putString(PREF_DATA_FOLDER, dir).apply() } @@ -743,7 +744,7 @@ object UserPreferences { Log.e(TAG, "Could not create .nomedia file") e.printStackTrace() } - Log.d(TAG, ".nomedia file created") + Logd(TAG, ".nomedia file created") } } diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/AutoDownloadPreferencesFragment.kt b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/AutoDownloadPreferencesFragment.kt index 05b27d76..6e9b6a11 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/AutoDownloadPreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/AutoDownloadPreferencesFragment.kt @@ -19,6 +19,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.autodownloadSelectedNetworks import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadWifiFilter import ac.mdiq.podcini.preferences.UserPreferences.setAutodownloadSelectedNetworks +import ac.mdiq.podcini.util.Logd import java.util.* class AutoDownloadPreferencesFragment : PreferenceFragmentCompat() { @@ -95,7 +96,7 @@ class AutoDownloadPreferencesFragment : PreferenceFragmentCompat() { val key = preference.getKey() val prefValuesList: MutableList = ArrayList(listOf(*autodownloadSelectedNetworks)) val newValue = preference.isChecked - Log.d(TAG, "Selected network $key. New state: $newValue") + Logd(TAG, "Selected network $key. New state: $newValue") val index = prefValuesList.indexOf(key) when { diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/WifiAuthenticationFragment.kt b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/WifiAuthenticationFragment.kt index 17478f34..ed306c6f 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/WifiAuthenticationFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/fragments/synchronization/WifiAuthenticationFragment.kt @@ -6,6 +6,7 @@ import ac.mdiq.podcini.net.sync.SynchronizationCredentials import ac.mdiq.podcini.net.sync.SynchronizationSettings.setWifiSyncEnabled import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.hostPort import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.startInstantSync +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.SyncServiceEvent import android.app.Dialog import android.content.Context.WIFI_SERVICE @@ -76,7 +77,7 @@ import java.util.* if (d != null) { val confirmButton = d.getButton(Dialog.BUTTON_POSITIVE) as Button confirmButton.setOnClickListener { - Log.d(TAG, "confirm button pressed") + Logd(TAG, "confirm button pressed") if (isGuest == null) { Toast.makeText(requireContext(), R.string.host_or_guest, Toast.LENGTH_LONG).show() return@setOnClickListener @@ -107,7 +108,7 @@ import java.util.* binding!!.progressBar.progress = event.message.toInt() } else -> { - Log.d(TAG, "Sync result unknow ${event.messageResId}") + Logd(TAG, "Sync result unknow ${event.messageResId}") // Toast.makeText(context, "Sync result unknow ${event.messageResId}", Toast.LENGTH_LONG).show() } } diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/ConnectivityActionReceiver.kt b/app/src/main/java/ac/mdiq/podcini/receiver/ConnectivityActionReceiver.kt index f14eb4fe..428c6cb0 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/ConnectivityActionReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/ConnectivityActionReceiver.kt @@ -9,11 +9,12 @@ import android.util.Log import androidx.media3.common.util.UnstableApi import ac.mdiq.podcini.util.config.ClientConfigurator import ac.mdiq.podcini.net.download.NetworkConnectionChangeHandler.networkChangedDetected +import ac.mdiq.podcini.util.Logd class ConnectivityActionReceiver : BroadcastReceiver() { @UnstableApi override fun onReceive(context: Context, intent: Intent) { if (TextUtils.equals(intent.action, ConnectivityManager.CONNECTIVITY_ACTION)) { - Log.d(TAG, "Received intent") + Logd(TAG, "Received intent") ClientConfigurator.initialize(context) networkChangedDetected() diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/FeedUpdateReceiver.kt b/app/src/main/java/ac/mdiq/podcini/receiver/FeedUpdateReceiver.kt index 953ef206..40d2eb0d 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/FeedUpdateReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/FeedUpdateReceiver.kt @@ -7,6 +7,7 @@ import android.util.Log import androidx.media3.common.util.UnstableApi import ac.mdiq.podcini.util.config.ClientConfigurator import ac.mdiq.podcini.net.download.FeedUpdateManager +import ac.mdiq.podcini.util.Logd /** * Refreshes all feeds when it receives an intent @@ -14,7 +15,7 @@ import ac.mdiq.podcini.net.download.FeedUpdateManager class FeedUpdateReceiver : BroadcastReceiver() { @UnstableApi override fun onReceive(context: Context, intent: Intent) { - Log.d(TAG, "Received intent") + Logd(TAG, "Received intent") ClientConfigurator.initialize(context) FeedUpdateManager.runOnce(context) 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 a7c220b1..a894d96d 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/MediaButtonReceiver.kt @@ -1,5 +1,6 @@ package ac.mdiq.podcini.receiver +import ac.mdiq.podcini.util.Logd import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context @@ -17,7 +18,7 @@ import ac.mdiq.podcini.util.config.ClientConfigurator class MediaButtonReceiver : BroadcastReceiver() { @UnstableApi override fun onReceive(context: Context, intent: Intent) { - Log.d(TAG, "Received intent") + Logd(TAG, "Received intent") if (intent.extras == null) return val event = intent.extras!![Intent.EXTRA_KEY_EVENT] as? KeyEvent diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/PlayerWidget.kt b/app/src/main/java/ac/mdiq/podcini/receiver/PlayerWidget.kt index 0e0b0953..2af3dbf7 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/PlayerWidget.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/PlayerWidget.kt @@ -9,19 +9,20 @@ import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker +import ac.mdiq.podcini.util.Logd import java.util.concurrent.TimeUnit class PlayerWidget : AppWidgetProvider() { override fun onEnabled(context: Context) { super.onEnabled(context) - Log.d(TAG, "Widget enabled") + Logd(TAG, "Widget enabled") setEnabled(context, true) WidgetUpdaterWorker.enqueueWork(context) scheduleWorkaround(context) } override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) { - Log.d(TAG, "onUpdate() called with: context = [$context], appWidgetManager = [$appWidgetManager], appWidgetIds = [${appWidgetIds.contentToString()}]") + Logd(TAG, "onUpdate() called with: context = [$context], appWidgetManager = [$appWidgetManager], appWidgetIds = [${appWidgetIds.contentToString()}]") WidgetUpdaterWorker.enqueueWork(context) val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) @@ -33,12 +34,12 @@ class PlayerWidget : AppWidgetProvider() { override fun onDisabled(context: Context) { super.onDisabled(context) - Log.d(TAG, "Widget disabled") + Logd(TAG, "Widget disabled") setEnabled(context, false) } override fun onDeleted(context: Context, appWidgetIds: IntArray) { - Log.d(TAG, "OnDeleted") + Logd(TAG, "OnDeleted") val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) for (appWidgetId in appWidgetIds) { prefs.edit().remove(KEY_WIDGET_COLOR + appWidgetId).apply() diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/PowerConnectionReceiver.kt b/app/src/main/java/ac/mdiq/podcini/receiver/PowerConnectionReceiver.kt index 2bc6bfd3..0a6b1920 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/PowerConnectionReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/PowerConnectionReceiver.kt @@ -9,6 +9,7 @@ import ac.mdiq.podcini.util.config.ClientConfigurator import ac.mdiq.podcini.storage.DBTasks import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery +import ac.mdiq.podcini.util.Logd // modified from http://developer.android.com/training/monitoring-device-state/battery-monitoring.html // and ConnectivityActionReceiver.java @@ -19,11 +20,11 @@ class PowerConnectionReceiver : BroadcastReceiver() { @UnstableApi override fun onReceive(context: Context, intent: Intent) { val action = intent.action - Log.d(TAG, "charging intent: $action") + Logd(TAG, "charging intent: $action") ClientConfigurator.initialize(context) if (Intent.ACTION_POWER_CONNECTED == action) { - Log.d(TAG, "charging, starting auto-download") + Logd(TAG, "charging, starting auto-download") // we're plugged in, this is a great time to auto-download if everything else is // right. So, even if the user allows auto-dl on battery, let's still start // downloading now. They shouldn't mind. @@ -33,9 +34,9 @@ class PowerConnectionReceiver : BroadcastReceiver() { } else { // if we're not supposed to be auto-downloading when we're not charging, stop it if (!isEnableAutodownloadOnBattery) { - Log.d(TAG, "not charging anymore, canceling auto-download") + Logd(TAG, "not charging anymore, canceling auto-download") DownloadServiceInterface.get()?.cancelAll(context) - } else Log.d(TAG, "not charging anymore, but the user allows auto-download when on battery so we'll keep going") + } else Logd(TAG, "not charging anymore, but the user allows auto-download when on battery so we'll keep going") } } diff --git a/app/src/main/java/ac/mdiq/podcini/receiver/SPAReceiver.kt b/app/src/main/java/ac/mdiq/podcini/receiver/SPAReceiver.kt index b5ee92ec..629270d1 100644 --- a/app/src/main/java/ac/mdiq/podcini/receiver/SPAReceiver.kt +++ b/app/src/main/java/ac/mdiq/podcini/receiver/SPAReceiver.kt @@ -12,6 +12,7 @@ import ac.mdiq.podcini.util.config.ClientConfigurator import ac.mdiq.podcini.storage.DBTasks import ac.mdiq.podcini.net.download.FeedUpdateManager.runOnce import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd /** * Receives intents from Podcini Single Purpose apps @@ -20,7 +21,7 @@ class SPAReceiver : BroadcastReceiver() { @UnstableApi override fun onReceive(context: Context, intent: Intent) { if (!TextUtils.equals(intent.action, ACTION_SP_APPS_QUERY_FEEDS_REPSONSE)) return - Log.d(TAG, "Received SP_APPS_QUERY_RESPONSE") + Logd(TAG, "Received SP_APPS_QUERY_RESPONSE") if (!intent.hasExtra(ACTION_SP_APPS_QUERY_FEEDS_REPSONSE_FEEDS_EXTRA)) { Log.e(TAG, "Received invalid SP_APPS_QUERY_RESPONSE: Contains no extra") return @@ -30,7 +31,7 @@ class SPAReceiver : BroadcastReceiver() { Log.e(TAG, "Received invalid SP_APPS_QUERY_REPSONSE: extra was null") return } - Log.d(TAG, "Received feeds list: " + feedUrls.contentToString()) + Logd(TAG, "Received feeds list: " + feedUrls.contentToString()) ClientConfigurator.initialize(context) for (url in feedUrls) { val feed = Feed(url, null, "Unknown podcast") diff --git a/app/src/main/java/ac/mdiq/podcini/storage/AutomaticDownloadAlgorithm.kt b/app/src/main/java/ac/mdiq/podcini/storage/AutomaticDownloadAlgorithm.kt index 9750c4d4..d05ade94 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/AutomaticDownloadAlgorithm.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/AutomaticDownloadAlgorithm.kt @@ -18,6 +18,7 @@ import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery +import ac.mdiq.podcini.util.Logd /** * Implements the automatic download algorithm used by Podcini. This class assumes that @@ -42,17 +43,17 @@ open class AutomaticDownloadAlgorithm { // true if we should auto download based on power status val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery) - Log.d(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl") + Logd(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl") // we should only auto download if both network AND power are happy if (networkShouldAutoDl && powerShouldAutoDl) { - Log.d(TAG, "Performing auto-dl of undownloaded episodes") + Logd(TAG, "Performing auto-dl of undownloaded episodes") val candidates: MutableList val queue = getQueue() val newItems = getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.NEW), SortOrder.DATE_NEW_OLD) - Log.d(TAG, "newItems: ${newItems.size}") + Logd(TAG, "newItems: ${newItems.size}") candidates = ArrayList(queue.size + newItems.size) candidates.addAll(queue) for (newItem in newItems) { @@ -80,14 +81,14 @@ open class AutomaticDownloadAlgorithm { val itemsToDownload: List = candidates.subList(0, episodeSpaceLeft) if (itemsToDownload.isNotEmpty()) { - Log.d(TAG, "Enqueueing " + itemsToDownload.size + " items for download") + Logd(TAG, "Enqueueing " + itemsToDownload.size + " items for download") for (episode in itemsToDownload) { DownloadServiceInterface.get()?.download(context, episode) } } } - else Log.d(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl") + else Logd(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl") } } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt index d902f353..505992f4 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt @@ -19,7 +19,6 @@ import ac.mdiq.podcini.util.comparator.DownloadResultComparator import ac.mdiq.podcini.util.comparator.PlaybackCompletionDateComparator import android.database.Cursor import android.util.Log -import androidx.collection.ArrayMap import kotlin.math.min @@ -30,8 +29,9 @@ import kotlin.math.min */ object DBReader { private const val TAG = "DBReader" - private var feeds: MutableList = mutableListOf() - private var tags: MutableList = mutableListOf() + private val feeds: MutableList = mutableListOf() + private val feedIndex: MutableMap = mutableMapOf() + private val tags: MutableList = mutableListOf() private val feedListLock = Any() /** @@ -61,11 +61,12 @@ object DBReader { fun updateFeedList(adapter: PodDBAdapter) { synchronized(feedListLock) { adapter.allFeedsCursor.use { cursor -> -// feeds = ArrayList(cursor.count) feeds.clear() + feedIndex.clear() while (cursor.moveToNext()) { val feed = extractFeedFromCursorRow(cursor) feeds.add(feed) + feedIndex[feed.id] = feed } buildTags() } @@ -110,21 +111,18 @@ object DBReader { * @param items the FeedItems who should have other data loaded */ fun loadAdditionalFeedItemListData(items: List) { - loadTagsOfFeedItemList(items) - - synchronized(feedListLock) { - loadFeedDataOfFeedItemList(items) - } - } - - private fun loadTagsOfFeedItemList(items: List) { - val favoriteIds = getFavoriteIDList() - val queueIds = getQueueIDList() + Logd(TAG, "loadAdditionalFeedItemListData called with items.size: ${items.size}") + val favoriteIds = getFavoriteIDSet() + val queueIds = getQueueIDSet() for (item in items) { if (favoriteIds.contains(item.id)) item.addTag(FeedItem.TAG_FAVORITE) if (queueIds.contains(item.id)) item.addTag(FeedItem.TAG_QUEUE) } + + synchronized(feedListLock) { + loadFeedDataOfFeedItemList(items) + } } /** @@ -136,11 +134,12 @@ object DBReader { */ private fun loadFeedDataOfFeedItemList(items: List) { Logd(TAG, "loadFeedDataOfFeedItemList called") - val feedIndex: MutableMap = ArrayMap(feeds.size) - val feedsCopy = ArrayList(feeds) - for (feed in feedsCopy) { - feedIndex[feed.id] = feed - } +// feedIndex is now a static member +// val feedIndex: MutableMap = ArrayMap(feeds.size) +// val feedsCopy = ArrayList(feeds) +// for (feed in feedsCopy) { +// feedIndex[feed.id] = feed +// } for (item in items) { var feed = feedIndex[item.feedId] if (feed == null) { @@ -168,9 +167,9 @@ object DBReader { return getFeedItemList(feed, filter, SortOrder.DATE_NEW_OLD) } - fun getFeedItemList(feed: Feed?, filter: FeedItemFilter?, sortOrder: SortOrder?): List { -// Log.d(TAG, "getFeedItemList() called with: feed = [$feed]") - + fun getFeedItemList(feed: Feed?, filter_: FeedItemFilter?, sortOrder: SortOrder?): List { + val filter = filter_ ?: unfiltered() + Logd(TAG, "getFeedItemList() called with: ${feed?.title}") val adapter = getInstance() adapter.open() try { @@ -240,8 +239,6 @@ object DBReader { @JvmStatic fun getQueueIDList(): LongList { Logd(TAG, "getQueueIDList() called") -// printStackTrace() - val adapter = getInstance() adapter.open() try { @@ -251,21 +248,39 @@ object DBReader { } } - private fun getQueueIDList(adapter: PodDBAdapter?): LongList { - adapter?.queueIDCursor?.use { cursor -> + private fun getQueueIDList(adapter: PodDBAdapter): LongList { + adapter.queueIDCursor.use { cursor -> val queueIds = LongList(cursor.count) while (cursor.moveToNext()) { queueIds.add(cursor.getLong(0)) } return queueIds } - return LongList() +// return LongList() + } + + @JvmStatic + fun getQueueIDSet(): HashSet { + Logd(TAG, "getQueueIDSet() called") + val adapter = getInstance() + adapter.open() + try { + adapter.queueIDCursor.use { cursor -> + val queueIds = HashSet(cursor.count) + while (cursor.moveToNext()) { + queueIds.add(cursor.getLong(0)) + } + return queueIds + } +// return HashSet() + } finally { + adapter.close() + } } @JvmStatic fun getQueue(): List { Logd(TAG, "getQueue() called") - val adapter = getInstance() adapter.open() try { @@ -275,14 +290,13 @@ object DBReader { } } - private fun getFavoriteIDList(): LongList { - Logd(TAG, "getFavoriteIDList() called") - + private fun getFavoriteIDSet(): HashSet { + Logd(TAG, "getFavoriteIDSet() called") val adapter = getInstance() adapter.open() try { adapter.getFavoritesIdsCursor(0, Int.MAX_VALUE).use { cursor -> - val favoriteIDs = LongList(cursor.count) + val favoriteIDs = HashSet(cursor.count) while (cursor.moveToNext()) { favoriteIDs.add(cursor.getLong(0)) } @@ -300,7 +314,8 @@ object DBReader { * @param filter The filter describing which episodes to filter out. */ @JvmStatic - fun getEpisodes(offset: Int, limit: Int, filter: FeedItemFilter?, sortOrder: SortOrder?): List { + fun getEpisodes(offset: Int, limit: Int, filter_: FeedItemFilter?, sortOrder: SortOrder?): List { + val filter = filter_ ?: unfiltered() Logd(TAG, "getEpisodes called with: offset=$offset, limit=$limit") val adapter = getInstance() adapter.open() @@ -316,7 +331,8 @@ object DBReader { } @JvmStatic - fun getTotalEpisodeCount(filter: FeedItemFilter?): Int { + fun getTotalEpisodeCount(filter_: FeedItemFilter?): Int { + val filter = filter_ ?: unfiltered() Logd(TAG, "getTotalEpisodeCount called") val adapter = getInstance() adapter.open() @@ -481,11 +497,11 @@ object DBReader { } } - private fun getFeedItem(itemId: Long, adapter: PodDBAdapter?): FeedItem? { + private fun getFeedItem(itemId: Long, adapter: PodDBAdapter): FeedItem? { Logd(TAG, "Loading feeditem with id $itemId") var item: FeedItem? = null - adapter?.getFeedItemCursor(itemId.toString())?.use { cursor -> + adapter.getFeedItemCursor(itemId.toString()).use { cursor -> if (cursor.moveToNext()) { val list = extractItemlistFromCursor(adapter, cursor) if (list.isNotEmpty()) { @@ -495,7 +511,7 @@ object DBReader { } return item } - return null +// return null } /** @@ -525,12 +541,13 @@ object DBReader { * @return The FeedItem next in queue or null if the FeedItem could not be found. */ fun getNextInQueue(item: FeedItem): FeedItem? { - Logd(TAG, "getNextInQueue() called with: itemId = [${item.id}]") + Logd(TAG, "*** expensive call getNextInQueue() with: itemId = [${item.id}]") val adapter = getInstance() adapter.open() try { var nextItem: FeedItem? = null try { +// TODO: these calls are expensive adapter.getNextInQueue(item).use { cursor -> val list = extractItemlistFromCursor(adapter, cursor) if (list.isNotEmpty()) { @@ -571,14 +588,14 @@ object DBReader { * @return The FeedItem or null if the FeedItem could not be found. * Does NOT load additional attributes like feed or queue state. */ - private fun getFeedItemByGuidOrEpisodeUrl(guid: String?, episodeUrl: String, adapter: PodDBAdapter?): FeedItem? { - adapter?.getFeedItemCursor(guid, episodeUrl)?.use { cursor -> + private fun getFeedItemByGuidOrEpisodeUrl(guid: String?, episodeUrl: String, adapter: PodDBAdapter): FeedItem? { + adapter.getFeedItemCursor(guid, episodeUrl).use { cursor -> if (!cursor.moveToNext()) return null val list = extractItemlistFromCursor(adapter, cursor) if (list.isNotEmpty()) return list[0] return null } - return null +// return null } /** @@ -598,9 +615,9 @@ object DBReader { } } - private fun getImageAuthentication(imageUrl: String, adapter: PodDBAdapter?): String { - var credentials = "" - adapter?.getImageAuthenticationCursor(imageUrl)?.use { cursor -> + private fun getImageAuthentication(imageUrl: String, adapter: PodDBAdapter): String { + var credentials: String + adapter.getImageAuthenticationCursor(imageUrl).use { cursor -> if (cursor.moveToFirst()) { val username = cursor.getString(0) val password = cursor.getString(1) @@ -646,10 +663,10 @@ object DBReader { if (cursor.moveToFirst()) { val indexDescription = cursor.getColumnIndex(PodDBAdapter.KEY_DESCRIPTION) val description = cursor.getString(indexDescription) - item.setDescriptionIfLonger(description) + item.setDescription(description) val indexTranscript = cursor.getColumnIndex(PodDBAdapter.KEY_TRANSCRIPT) val transcript = cursor.getString(indexTranscript) - item.setTranscriptIfLonger(transcript) + item.setTranscript(transcript) } } } finally { @@ -681,8 +698,8 @@ object DBReader { } } - private fun loadChaptersOfFeedItem(adapter: PodDBAdapter?, item: FeedItem): List? { - adapter?.getSimpleChaptersOfFeedItemCursor(item)?.use { cursor -> + private fun loadChaptersOfFeedItem(adapter: PodDBAdapter, item: FeedItem): List? { + adapter.getSimpleChaptersOfFeedItemCursor(item).use { cursor -> val chaptersCount = cursor.count if (chaptersCount == 0) { item.chapters = null @@ -694,7 +711,7 @@ object DBReader { } return chapters } - return null +// return null } /** @@ -827,7 +844,7 @@ object DBReader { val adapter = getInstance() adapter.open() - val feedCounters: Map = adapter.getFeedCounters(feedCounterSetting) + val feedCounters: Map = adapter.getFeedEpisodesCounters(feedCounterSetting) // getFeedList(adapter) // TODO: @@ -901,16 +918,19 @@ object DBReader { } val queueSize = adapter.queueSize +// getting all items, not only new ones val numNewItems = getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.NEW)) val numDownloadedItems = getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.DOWNLOADED)) - + val numItems = getTotalEpisodeCount(unfiltered()) + val numFeeds = feeds.size val items: MutableList = ArrayList() for (feed in feeds) { val counter = if (feedCounters.containsKey(feed.id)) feedCounters[feed.id]!! else 0 val drawerItem = FeedDrawerItem(feed, feed.id, counter) items.add(drawerItem) } - val result = NavDrawerData(items, queueSize, numNewItems, numDownloadedItems, feedCounters, EpisodeCleanupAlgorithmFactory.build().getReclaimableItems()) + val result = NavDrawerData(items, queueSize, numNewItems, numDownloadedItems, feedCounters, + EpisodeCleanupAlgorithmFactory.build().getReclaimableItems(), numItems, numFeeds) adapter.close() return result } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DBTasks.kt b/app/src/main/java/ac/mdiq/podcini/storage/DBTasks.kt index c26ceff1..05604ef3 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DBTasks.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DBTasks.kt @@ -16,6 +16,7 @@ import ac.mdiq.podcini.storage.model.download.DownloadResult import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedMedia +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.comparator.FeedItemPubdateComparator import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated import ac.mdiq.podcini.util.event.FeedListUpdateEvent @@ -105,7 +106,7 @@ import java.util.concurrent.* */ @UnstableApi @JvmStatic fun autodownloadUndownloadedItems(context: Context): Future<*> { - Log.d(TAG, "autodownloadUndownloadedItems") + Logd(TAG, "autodownloadUndownloadedItems") return autodownloadExec.submit(downloadAlgorithm.autoDownloadUndownloadedItems(context)) } @@ -190,7 +191,7 @@ import java.util.concurrent.* @JvmStatic @Synchronized fun updateFeed(context: Context, newFeed: Feed, removeUnlistedItems: Boolean): Feed? { - Log.d(TAG, "updateFeed called") + Logd(TAG, "updateFeed called") var resultFeed: Feed? val unlistedItems: MutableList = ArrayList() @@ -200,24 +201,24 @@ import java.util.concurrent.* // Look up feed in the feedslist val savedFeed = searchFeedByIdentifyingValueOrID(newFeed) if (savedFeed == null) { - Log.d(TAG, "Found no existing Feed with title " + newFeed.title + ". Adding as new one.") + Logd(TAG, "Found no existing Feed with title " + newFeed.title + ". Adding as new one.") resultFeed = newFeed } else { - Log.d(TAG, "Feed with title " + newFeed.title + " already exists. Syncing new with existing one.") + Logd(TAG, "Feed with title " + newFeed.title + " already exists. Syncing new with existing one.") newFeed.items.sortWith(FeedItemPubdateComparator()) if (newFeed.pageNr == savedFeed.pageNr) { if (savedFeed.compareWithOther(newFeed)) { - Log.d(TAG, "Feed has updated attribute values. Updating old feed's attributes") + Logd(TAG, "Feed has updated attribute values. Updating old feed's attributes") savedFeed.updateFromOther(newFeed) } } else { - Log.d(TAG, "New feed has a higher page number.") + Logd(TAG, "New feed has a higher page number.") savedFeed.nextPageLink = newFeed.nextPageLink } if (savedFeed.preferences != null && savedFeed.preferences!!.compareWithOther(newFeed.preferences)) { - Log.d(TAG, "Feed has updated preferences. Updating old feed's preferences") + Logd(TAG, "Feed has updated preferences. Updating old feed's preferences") savedFeed.preferences!!.updateFromOther(newFeed.preferences) } @@ -249,7 +250,7 @@ import java.util.concurrent.* if (!newFeed.isLocalFeed && oldItem == null) { oldItem = searchFeedItemGuessDuplicate(savedFeed.items, item) if (oldItem != null) { - Log.d(TAG, "Repaired duplicate: $oldItem, $item") + Logd(TAG, "Repaired duplicate: $oldItem, $item") DBWriter.addDownloadStatus(DownloadResult(savedFeed, item.title?:"", DownloadError.ERROR_PARSER_EXCEPTION_DUPLICATE, false, """ @@ -279,7 +280,7 @@ import java.util.concurrent.* if (oldItem != null) { oldItem.updateFromOther(item) } else { - Log.d(TAG, "Found new item: " + item.title) + Logd(TAG, "Found new item: " + item.title) item.feed = savedFeed if (idx >= savedFeed.items.size) savedFeed.items.add(item) @@ -287,7 +288,7 @@ import java.util.concurrent.* val pubDate = item.getPubDate() if (pubDate == null || priorMostRecentDate == null || priorMostRecentDate.before(pubDate) || priorMostRecentDate == pubDate) { - Log.d(TAG, "Marking item published on $pubDate new, prior most recent date = $priorMostRecentDate") + Logd(TAG, "Marking item published on $pubDate new, prior most recent date = $priorMostRecentDate") item.setNew() } } @@ -357,7 +358,7 @@ import java.util.concurrent.* */ @JvmStatic fun searchFeedItems(feedID: Long, query: String): FutureTask> { - Log.d(TAG, "searchFeedItems called") + Logd(TAG, "searchFeedItems called") return FutureTask(object : QueryTask>() { override fun execute(adapter: PodDBAdapter?) { val searchResult = adapter?.searchItems(feedID, query) diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DBWriter.kt b/app/src/main/java/ac/mdiq/podcini/storage/DBWriter.kt index c1e393c1..a4a66277 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DBWriter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DBWriter.kt @@ -43,6 +43,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation import ac.mdiq.podcini.preferences.UserPreferences.isQueueKeepSorted import ac.mdiq.podcini.preferences.UserPreferences.queueKeepSortedOrder import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.showStackTrace import org.greenrobot.eventbus.EventBus import java.io.File @@ -98,7 +99,7 @@ import java.util.concurrent.TimeUnit */ @JvmStatic fun deleteFeedMediaOfItem(context: Context, mediaId: Long): Future<*> { - Log.d(TAG, "deleteFeedMediaOfItem called") + Logd(TAG, "deleteFeedMediaOfItem called") return runOnDbThread { val media = getFeedMedia(mediaId) if (media != null) { @@ -208,7 +209,7 @@ import java.util.concurrent.TimeUnit * Deleting media also removes the download log entries. */ fun deleteFeedItems(context: Context, items: List): Future<*> { - Log.d(TAG, "deleteFeedItems called") + Logd(TAG, "deleteFeedItems called") return runOnDbThread { deleteFeedItemsSynchronous(context, items) } } @@ -295,7 +296,7 @@ import java.util.concurrent.TimeUnit fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> { return runOnDbThread { if (media != null) { - Log.d(TAG, "Adding item to playback history") + Logd(TAG, "Adding item to playback history") media.setPlaybackCompletionDate(date) val adapter = getInstance() @@ -313,7 +314,7 @@ import java.util.concurrent.TimeUnit * @param status The DownloadStatus object. */ fun addDownloadStatus(status: DownloadResult?): Future<*> { - Log.d(TAG, "addDownloadStatus called") + Logd(TAG, "addDownloadStatus called") return runOnDbThread { if (status != null) { val adapter = getInstance() @@ -336,7 +337,7 @@ import java.util.concurrent.TimeUnit * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size() */ @UnstableApi fun addQueueItemAt(context: Context, itemId: Long, index: Int, performAutoDownload: Boolean): Future<*> { - Log.d(TAG, "addQueueItemAt called") + Logd(TAG, "addQueueItemAt called") return runOnDbThread { val adapter = getInstance() adapter.open() @@ -366,7 +367,7 @@ import java.util.concurrent.TimeUnit } fun addQueueItem(context: Context, markAsUnplayed: Boolean, vararg items: FeedItem): Future<*> { - Log.d(TAG, "addQueueItem called") + Logd(TAG, "addQueueItem called") val itemIds = LongList(items.size) for (item in items) { if (!item.hasMedia()) continue @@ -398,7 +399,7 @@ import java.util.concurrent.TimeUnit * @param itemIds IDs of the FeedItem objects that should be added to the queue. */ @UnstableApi fun addQueueItem(context: Context, performAutoDownload: Boolean, markAsUnplayed: Boolean, vararg itemIds: Long): Future<*> { - Log.d(TAG, "addQueueItem(context ...) called") + Logd(TAG, "addQueueItem(context ...) called") return runOnDbThread { if (itemIds.isEmpty()) return@runOnDbThread @@ -500,7 +501,7 @@ import java.util.concurrent.TimeUnit } @UnstableApi private fun removeQueueItemSynchronous(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) { - Log.d(TAG, "removeQueueItemSynchronous called $itemIds") + Logd(TAG, "removeQueueItemSynchronous called $itemIds") if (itemIds.isEmpty()) return showStackTrace() @@ -515,7 +516,7 @@ import java.util.concurrent.TimeUnit val position = indexInItemList(queue, itemId) if (position >= 0) { val item = getFeedItem(itemId) - Log.d(TAG, "removing item from queue: ${item?.title}") + Logd(TAG, "removing item from queue: ${item?.title}") if (item == null) { Log.e(TAG, "removeQueueItem - item in queue but somehow cannot be loaded. Item ignored. It should never happen. id:$itemId") continue @@ -776,7 +777,7 @@ import java.util.concurrent.TimeUnit * @param media The FeedMedia object. */ fun persistFeedMedia(media: FeedMedia): Future<*> { - Log.d(TAG, "persistFeedMedia called") + Logd(TAG, "persistFeedMedia called") return runOnDbThread { val adapter = getInstance() adapter.open() @@ -792,7 +793,7 @@ import java.util.concurrent.TimeUnit */ @JvmStatic fun persistFeedMediaPlaybackInfo(media: FeedMedia?): Future<*> { - Log.d(TAG, "persistFeedMediaPlaybackInfo called") + Logd(TAG, "persistFeedMediaPlaybackInfo called") return runOnDbThread { if (media != null) { val adapter = getInstance() @@ -811,7 +812,7 @@ import java.util.concurrent.TimeUnit */ @JvmStatic fun persistFeedItem(item: FeedItem?): Future<*> { - Log.d(TAG, "persistFeedItem called") + Logd(TAG, "persistFeedItem called") return runOnDbThread { if (item != null) { val adapter = getInstance() @@ -827,7 +828,7 @@ import java.util.concurrent.TimeUnit * Updates download URL of a feed */ fun updateFeedDownloadURL(original: String, updated: String): Future<*> { - Log.d(TAG, "updateFeedDownloadURL(original: $original, updated: $updated)") + Logd(TAG, "updateFeedDownloadURL(original: $original, updated: $updated)") return runOnDbThread { val adapter = getInstance() adapter.open() @@ -856,7 +857,7 @@ import java.util.concurrent.TimeUnit } private fun indexInItemList(items: List, itemId: Long): Int { - Log.d(TAG, "indexInItemList called") + Logd(TAG, "indexInItemList called") for (i in items.indices) { val item = items[i] if (item?.id == itemId) return i @@ -921,7 +922,7 @@ import java.util.concurrent.TimeUnit * @param filterValues Values that represent properties to filter by */ fun persistFeedItemsFilter(feedId: Long, filterValues: Set): Future<*> { - Log.d(TAG, "persistFeedItemsFilter() called with: feedId = [$feedId], filterValues = [$filterValues]") + Logd(TAG, "persistFeedItemsFilter() called with: feedId = [$feedId], filterValues = [$filterValues]") return runOnDbThread { val adapter = getInstance() adapter.open() diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DatabaseTransporter.kt b/app/src/main/java/ac/mdiq/podcini/storage/DatabaseTransporter.kt index 93c930b8..3dce3789 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DatabaseTransporter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DatabaseTransporter.kt @@ -9,6 +9,7 @@ import android.os.ParcelFileDescriptor import android.text.format.Formatter import android.util.Log import ac.mdiq.podcini.storage.database.PodDBAdapter +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.FileUtils import org.apache.commons.io.IOUtils import java.io.FileInputStream @@ -39,7 +40,7 @@ object DatabaseTransporter { try { pfd.close() } catch (e: IOException) { - Log.d(TAG, "Unable to close ParcelFileDescriptor") + Logd(TAG, "Unable to close ParcelFileDescriptor") } } } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/NavDrawerData.kt b/app/src/main/java/ac/mdiq/podcini/storage/NavDrawerData.kt index 0eb75f49..1ab8192d 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/NavDrawerData.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/NavDrawerData.kt @@ -7,8 +7,10 @@ class NavDrawerData(@JvmField val items: List, @JvmField val numNewItems: Int, val numDownloadedItems: Int, val feedCounters: Map, - val reclaimableSpace: Int -) { + val reclaimableSpace: Int, + @JvmField val numItems: Int, + @JvmField val numFeeds: Int) { + class FeedDrawerItem(val feed: Feed, val id: Long, val counter: Int) { var layer: Int = 0 diff --git a/app/src/main/java/ac/mdiq/podcini/storage/backup/OpmlBackupAgent.kt b/app/src/main/java/ac/mdiq/podcini/storage/backup/OpmlBackupAgent.kt index e85a9450..051584bf 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/backup/OpmlBackupAgent.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/backup/OpmlBackupAgent.kt @@ -13,6 +13,7 @@ import ac.mdiq.podcini.storage.DBReader.getFeedList import ac.mdiq.podcini.storage.DBTasks.updateFeed import ac.mdiq.podcini.net.download.FeedUpdateManager.runOnce import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd import androidx.annotation.OptIn import androidx.media3.common.util.UnstableApi import org.apache.commons.io.IOUtils @@ -40,7 +41,7 @@ class OpmlBackupAgent : BackupAgentHelper() { private var mChecksum: ByteArray = byteArrayOf() override fun performBackup(oldState: ParcelFileDescriptor, data: BackupDataOutput, newState: ParcelFileDescriptor) { - Log.d(TAG, "Performing backup") + Logd(TAG, "Performing backup") val byteStream = ByteArrayOutputStream() var digester: MessageDigest? = null var writer: Writer @@ -59,7 +60,7 @@ class OpmlBackupAgent : BackupAgentHelper() { // Compare checksum of new and old file to see if we need to perform a backup at all if (digester != null) { val newChecksum = digester.digest() - Log.d(TAG, "New checksum: " + BigInteger(1, newChecksum).toString(16)) + Logd(TAG, "New checksum: " + BigInteger(1, newChecksum).toString(16)) // Get the old checksum if (oldState != null) { @@ -69,10 +70,10 @@ class OpmlBackupAgent : BackupAgentHelper() { if (len != -1) { val oldChecksum = ByteArray(len) IOUtils.read(inState, oldChecksum, 0, len) - Log.d(TAG, "Old checksum: " + BigInteger(1, oldChecksum).toString(16)) + Logd(TAG, "Old checksum: " + BigInteger(1, oldChecksum).toString(16)) if (oldChecksum.contentEquals(newChecksum)) { - Log.d(TAG, "Checksums are the same; won't backup") + Logd(TAG, "Checksums are the same; won't backup") return } } @@ -81,7 +82,7 @@ class OpmlBackupAgent : BackupAgentHelper() { writeNewStateDescription(newState, newChecksum) } - Log.d(TAG, "Backing up OPML") + Logd(TAG, "Backing up OPML") val bytes = byteStream.toByteArray() data.writeEntityHeader(OPML_ENTITY_KEY, bytes.size) data.writeEntityData(bytes, bytes.size) @@ -93,10 +94,10 @@ class OpmlBackupAgent : BackupAgentHelper() { } @OptIn(UnstableApi::class) override fun restoreEntity(data: BackupDataInputStream) { - Log.d(TAG, "Backup restore") + Logd(TAG, "Backup restore") if (OPML_ENTITY_KEY != data.key) { - Log.d(TAG, "Unknown entity key: " + data.key) + Logd(TAG, "Unknown entity key: " + data.key) return } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/database/DBUpgrader.kt b/app/src/main/java/ac/mdiq/podcini/storage/database/DBUpgrader.kt index 25d9b295..9fe691e0 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/database/DBUpgrader.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/database/DBUpgrader.kt @@ -6,6 +6,7 @@ import android.media.MediaMetadataRetriever import android.util.Log import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedPreferences +import ac.mdiq.podcini.util.Logd internal object DBUpgrader { /** @@ -155,7 +156,7 @@ internal object DBUpgrader { + PodDBAdapter.TABLE_NAME_FEED_MEDIA + "." + PodDBAdapter.KEY_POSITION + " = 0 AND " // not partially played + PodDBAdapter.TABLE_NAME_QUEUE + "." + PodDBAdapter.KEY_ID + " IS NULL") // not in queue val sql = ("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + " SET " + PodDBAdapter.KEY_READ + "=" + FeedItem.NEW + " WHERE " + PodDBAdapter.KEY_ID + " IN (" + selectNew + ")") - Log.d("Migration", "SQL: $sql") + Logd("Migration", "SQL: $sql") db.execSQL(sql) } if (oldVersion <= 17) { diff --git a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt index 6bf33248..80024641 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt @@ -14,6 +14,7 @@ import ac.mdiq.podcini.storage.model.feed.* import ac.mdiq.podcini.storage.model.feed.SortOrder.Companion.toCodeString import ac.mdiq.podcini.storage.database.mapper.FeedItemFilterQuery.generateFrom import ac.mdiq.podcini.storage.database.mapper.FeedItemSortQuery.generateFrom +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.FileUtils import java.io.File import java.io.IOException @@ -77,20 +78,17 @@ class PodDBAdapter private constructor() { values.put(KEY_IS_PAGED, feed.isPaged) values.put(KEY_NEXT_PAGE_LINK, feed.nextPageLink) - if (!feed.itemFilter?.values.isNullOrEmpty()) { - values.put(KEY_HIDE, TextUtils.join(",", feed.itemFilter!!.values)) - } else { - values.put(KEY_HIDE, "") - } - values.put(KEY_SORT_ORDER, toCodeString( - feed.sortOrder)) + if (!feed.itemFilter?.values.isNullOrEmpty()) values.put(KEY_HIDE, TextUtils.join(",", feed.itemFilter!!.values)) + else values.put(KEY_HIDE, "") + + values.put(KEY_SORT_ORDER, toCodeString(feed.sortOrder)) values.put(KEY_LAST_UPDATE_FAILED, feed.hasLastUpdateFailed()) if (feed.id == 0L) { // Create new entry - Log.d(this.toString(), "Inserting new Feed into db") + Logd(this.toString(), "Inserting new Feed into db") feed.id = db.insert(TABLE_NAME_FEEDS, null, values) } else { - Log.d(this.toString(), "Updating existing Feed in db") + Logd(this.toString(), "Updating existing Feed in db") db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(feed.id.toString())) } return feed.id @@ -120,7 +118,7 @@ class PodDBAdapter private constructor() { fun setFeedItemFilter(feedId: Long, filterValues: Set?) { val valuesList = TextUtils.join(",", filterValues!!) - Log.d(TAG, String.format(Locale.US, "setFeedItemFilter() called with: feedId = [%d], filterValues = [%s]", feedId, valuesList)) + Logd(TAG, String.format(Locale.US, "setFeedItemFilter() called with: feedId = [%d], filterValues = [%s]", feedId, valuesList)) val values = ContentValues() values.put(KEY_HIDE, valuesList) db.update(TABLE_NAME_FEEDS, values, "$KEY_ID=?", arrayOf(feedId.toString())) @@ -419,7 +417,7 @@ class PodDBAdapter private constructor() { fun addFavoriteItem(item: FeedItem) { // don't add an item that's already there... if (isItemInFavorites(item)) { - Log.d(TAG, "item already in favorites") + Logd(TAG, "item already in favorites") return } val values = ContentValues() @@ -570,7 +568,7 @@ class PodDBAdapter private constructor() { * @param feed The feed you want to get the FeedItems from. * @return The cursor of the query */ - fun getItemsOfFeedCursor(feed: Feed, filter: FeedItemFilter?): Cursor { + fun getItemsOfFeedCursor(feed: Feed, filter: FeedItemFilter): Cursor { val filterQuery = generateFrom(filter!!) val whereClauseAnd = if ("" == filterQuery) "" else " AND $filterQuery" val query = ("$SELECT_FEED_ITEMS_AND_MEDIA WHERE $TABLE_NAME_FEED_ITEMS.$KEY_FEED=${feed.id}$whereClauseAnd") @@ -649,7 +647,7 @@ class PodDBAdapter private constructor() { db.execSQL(sql) } - fun getEpisodesCursor(offset: Int, limit: Int, filter: FeedItemFilter?, sortOrder: SortOrder?): Cursor { + fun getEpisodesCursor(offset: Int, limit: Int, filter: FeedItemFilter, sortOrder: SortOrder?): Cursor { val orderByQuery = generateFrom(sortOrder) val filterQuery = generateFrom(filter!!) val whereClause = if ("" == filterQuery) "" else " WHERE $filterQuery" @@ -657,7 +655,7 @@ class PodDBAdapter private constructor() { return db.rawQuery(query, null) } - fun getEpisodeCountCursor(filter: FeedItemFilter?): Cursor { + fun getEpisodeCountCursor(filter: FeedItemFilter): Cursor { val filterQuery = generateFrom(filter!!) val whereClause = if ("" == filterQuery) "" else " WHERE $filterQuery" val query = ("SELECT count($TABLE_NAME_FEED_ITEMS.$KEY_ID) FROM $TABLE_NAME_FEED_ITEMS$JOIN_FEED_ITEM_AND_MEDIA$whereClause") @@ -787,9 +785,9 @@ class PodDBAdapter private constructor() { return result } - fun getFeedCounters(setting: FeedCounter?, vararg feedIds: Long): Map { + fun getFeedEpisodesCounters(setting: FeedCounter?, vararg feedIds: Long): Map { val whereRead = when (setting) { -// FeedCounter.SHOW_NEW -> KEY_READ + "=" + FeedItem.NEW + FeedCounter.SHOW_NEW -> KEY_READ + "=" + FeedItem.NEW FeedCounter.SHOW_UNPLAYED -> ("(" + KEY_READ + "=" + FeedItem.NEW + " OR " + KEY_READ + "=" + FeedItem.UNPLAYED + ")") FeedCounter.SHOW_DOWNLOADED -> "$KEY_DOWNLOADED=1" FeedCounter.SHOW_DOWNLOADED_UNPLAYED -> ("(" + KEY_READ + "=" + FeedItem.NEW @@ -798,10 +796,10 @@ class PodDBAdapter private constructor() { FeedCounter.SHOW_NONE -> return HashMap() else -> return HashMap() } - return conditionalFeedCounterRead(whereRead, *feedIds) + return conditionalFeedEpisodesCounterRead(whereRead, *feedIds) } - private fun conditionalFeedCounterRead(whereRead: String, vararg feedIds: Long): Map { + private fun conditionalFeedEpisodesCounterRead(whereRead: String, vararg feedIds: Long): Map { var limitFeeds = "" if (feedIds.isNotEmpty()) { // work around TextUtils.join wanting only boxed items @@ -833,7 +831,7 @@ class PodDBAdapter private constructor() { fun getPlayedEpisodesCounters(vararg feedIds: Long): Map { val whereRead = KEY_READ + "=" + FeedItem.PLAYED - return conditionalFeedCounterRead(whereRead, *feedIds) + return conditionalFeedEpisodesCounterRead(whereRead, *feedIds) } val mostRecentItemDates: Map @@ -968,9 +966,9 @@ class PodDBAdapter private constructor() { val backupFile = File(backupFolder, "CorruptedDatabaseBackup.db") try { FileUtils.copyFile(dbPath, backupFile) - Log.d(TAG, "Dumped database to " + backupFile.path) + Logd(TAG, "Dumped database to " + backupFile.path) } catch (e: IOException) { - Log.d(TAG, Log.getStackTraceString(e)) + Logd(TAG, Log.getStackTraceString(e)) } DefaultDatabaseErrorHandler().onCorruption(db) // This deletes the database 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 c3f21311..8ccf2797 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 @@ -8,6 +8,7 @@ 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 org.apache.commons.io.IOUtils import java.io.IOException import java.io.Writer @@ -17,7 +18,7 @@ import java.util.* class FavoritesWriter : ExportWriter { @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) override fun writeDocument(feeds: List?, writer: Writer?, context: Context) { - Log.d(TAG, "Starting to write document") + Logd(TAG, "Starting to write document") val templateStream = context!!.assets.open("html-export-template.html") var template = IOUtils.toString(templateStream, UTF_8) @@ -50,7 +51,7 @@ class FavoritesWriter : ExportWriter { writer.append(templateParts[1]) - Log.d(TAG, "Finished writing document") + Logd(TAG, "Finished writing document") } /** diff --git a/app/src/main/java/ac/mdiq/podcini/storage/export/html/HtmlWriter.kt b/app/src/main/java/ac/mdiq/podcini/storage/export/html/HtmlWriter.kt index 1850e379..9d6f7ea1 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/export/html/HtmlWriter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/export/html/HtmlWriter.kt @@ -4,6 +4,7 @@ import android.content.Context import android.util.Log import ac.mdiq.podcini.storage.export.ExportWriter import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd import org.apache.commons.io.IOUtils import java.io.IOException import java.io.Writer @@ -16,7 +17,7 @@ class HtmlWriter : ExportWriter { */ @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) override fun writeDocument(feeds: List?, writer: Writer?, context: Context) { - Log.d(TAG, "Starting to write document") + Logd(TAG, "Starting to write document") val templateStream = context!!.assets.open("html-export-template.html") var template = IOUtils.toString(templateStream, "UTF-8") @@ -36,7 +37,7 @@ class HtmlWriter : ExportWriter { writer.append("\">Feed

\n") } writer.append(templateParts[1]) - Log.d(TAG, "Finished writing document") + Logd(TAG, "Finished writing document") } override fun fileExtension(): String { diff --git a/app/src/main/java/ac/mdiq/podcini/storage/export/opml/OpmlWriter.kt b/app/src/main/java/ac/mdiq/podcini/storage/export/opml/OpmlWriter.kt index fe5fa60f..7d36d0eb 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/export/opml/OpmlWriter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/export/opml/OpmlWriter.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.export.CommonSymbols import ac.mdiq.podcini.storage.export.ExportWriter import ac.mdiq.podcini.util.DateFormatter.formatRfc822Date import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.util.Logd import java.io.IOException import java.io.Writer import java.util.* @@ -19,7 +20,7 @@ class OpmlWriter : ExportWriter { */ @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) override fun writeDocument(feeds: List?, writer: Writer?, context: Context) { - Log.d(TAG, "Starting to write document") + Logd(TAG, "Starting to write document") val xs = Xml.newSerializer() xs.setFeature(CommonSymbols.XML_FEATURE_INDENT_OUTPUT, true) xs.setOutput(writer) @@ -50,7 +51,7 @@ class OpmlWriter : ExportWriter { xs.endTag(null, CommonSymbols.BODY) xs.endTag(null, OpmlSymbols.OPML) xs.endDocument() - Log.d(TAG, "Finished writing document") + Logd(TAG, "Finished writing document") } override fun fileExtension(): String { diff --git a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedCounter.kt b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedCounter.kt index b83ceaa5..bb09bcbf 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedCounter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedCounter.kt @@ -1,7 +1,7 @@ package ac.mdiq.podcini.storage.model.feed enum class FeedCounter(val id: Int) { -// SHOW_NEW(1), + SHOW_NEW(1), SHOW_UNPLAYED(2), SHOW_NONE(3), SHOW_DOWNLOADED(4), diff --git a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedItem.kt b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedItem.kt index 5bead939..5bcddf9f 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedItem.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/FeedItem.kt @@ -221,6 +221,14 @@ class FeedItem : FeedComponent, Serializable { val isInProgress: Boolean get() = (media != null && media!!.isInProgress) + fun setDescription(newDescription: String?) { + this.description = newDescription + } + + fun setTranscript(newTranscript: String?) { + this.transcript = newTranscript + } + /** * Updates this item's description property if the given argument is longer than the already stored description * @param newDescription The new item description, content:encoded, itunes:description, etc. diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/ItemActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/ItemActionButton.kt index dc0cf5b2..8eb334e8 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/ItemActionButton.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/ItemActionButton.kt @@ -11,7 +11,7 @@ import android.widget.ImageView import androidx.media3.common.util.UnstableApi abstract class ItemActionButton internal constructor(@JvmField var item: FeedItem) { - val TAG = this::class.simpleName + val TAG = this::class.simpleName ?: "ItemActionButton" abstract fun getLabel(): Int diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt index c361a2b6..f3b38af4 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/PlayActionButton.kt @@ -32,10 +32,10 @@ class PlayActionButton(item: FeedItem) : ItemActionButton(item) { return } - EventBus.getDefault().post(StartPlayEvent(item)) PlaybackServiceStarter(context, media) .callEvenIfRunning(true) .start() + EventBus.getDefault().post(StartPlayEvent(item)) if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO)) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt index 4ff3b8d5..6392c015 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/StreamActionButton.kt @@ -36,11 +36,11 @@ class StreamActionButton(item: FeedItem) : ItemActionButton(item) { return } - EventBus.getDefault().post(StartPlayEvent(item)) PlaybackServiceStarter(context, media) .shouldStreamThisTime(true) .callEvenIfRunning(true) .start() + EventBus.getDefault().post(StartPlayEvent(item)) if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO)) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/TTSActionButton.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/TTSActionButton.kt index 9276ee5a..86f0132d 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/TTSActionButton.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/actionbutton/TTSActionButton.kt @@ -11,6 +11,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.tts import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.ttsReady import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment.Companion.ttsWorking +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.NetworkUtils.fetchHtmlSource import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated import android.content.Context @@ -44,7 +45,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) { } @OptIn(UnstableApi::class) override fun onClick(context: Context) { - Log.d("TTSActionButton", "onClick called") + Logd("TTSActionButton", "onClick called") if (item.link.isNullOrEmpty()) { Toast.makeText(context, R.string.episode_has_no_content, Toast.LENGTH_LONG).show() return @@ -61,7 +62,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) { readerText = article.textContent item.setTranscriptIfLonger(article.contentWithDocumentsCharsetOrUtf8) persistFeedItem(item) - Log.d(TAG, "readability4J: ${readerText?.substring(max(0, readerText!!.length-100), readerText!!.length)}") + Logd(TAG, "readability4J: ${readerText?.substring(max(0, readerText!!.length-100), readerText!!.length)}") } } else readerText = HtmlCompat.fromHtml(item.transcript!!, HtmlCompat.FROM_HTML_MODE_COMPACT).toString() processing = 0.1f @@ -87,7 +88,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) { override fun onStart(utteranceId: String?) {} override fun onDone(utteranceId: String?) { j++ - Log.d(TAG, "onDone ${mediaFile.length()} $utteranceId") + Logd(TAG, "onDone ${mediaFile.length()} $utteranceId") } @Deprecated("Deprecated in Java") override fun onError(utteranceId: String) { @@ -100,20 +101,20 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) { } }) - Log.d(TAG, "readerText: ${readerText?.length}") + Logd(TAG, "readerText: ${readerText?.length}") var startIndex = 0 var i = 0 val parts = mutableListOf() val chunkLength = getMaxSpeechInputLength() var status = TextToSpeech.ERROR while (startIndex < readerText!!.length) { - Log.d(TAG, "working on chunk $i $startIndex") + Logd(TAG, "working on chunk $i $startIndex") val endIndex = minOf(startIndex + chunkLength, readerText!!.length) val chunk = readerText!!.substring(startIndex, endIndex) val tempFile = File.createTempFile("tts_temp_${i}_", ".wav") parts.add(tempFile.absolutePath) status = tts?.synthesizeToFile(chunk, null, tempFile, tempFile.absolutePath) ?: 0 - Log.d(TAG, "status: $status chunk: ${chunk.substring(0, min(80, chunk.length))}") + Logd(TAG, "status: $status chunk: ${chunk.substring(0, min(80, chunk.length))}") if (status == TextToSpeech.ERROR) { withContext(Dispatchers.Main) { Toast.makeText(context, "Error generating audio file $tempFile.absolutePath", Toast.LENGTH_LONG).show() } break @@ -138,7 +139,7 @@ class TTSActionButton(item: FeedItem) : ItemActionButton(item) { // } // } val mFilename = mediaFile.absolutePath - Log.d(TAG, "saving TTS to file $mFilename") + Logd(TAG, "saving TTS to file $mFilename") val media = FeedMedia(item, null, 0, "audio/*") media.setFile_url(mFilename) media.setDownloaded(true) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt b/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt index 33d07b3a..95f6a909 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/actions/menuhandler/FeedItemMenuHandler.kt @@ -186,7 +186,7 @@ object FeedItemMenuHandler { shareDialog.show((fragment.requireActivity().supportFragmentManager), "ShareEpisodeDialog") } else -> { - Log.d(TAG, "Unknown menuItemId: $menuItemId") + Logd(TAG, "Unknown menuItemId: $menuItemId") return false } } @@ -204,7 +204,7 @@ object FeedItemMenuHandler { fun markReadWithUndo(fragment: Fragment, item: FeedItem?, playState: Int, showSnackbar: Boolean) { if (item == null) return - Log.d(TAG, "markReadWithUndo(" + item.id + ")") + Logd(TAG, "markReadWithUndo(" + item.id + ")") // we're marking it as unplayed since the user didn't actually play it // but they don't want it considered 'NEW' anymore DBWriter.markItemPlayed(playState, item.id) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt index 993acff4..f2d7fb9a 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt @@ -5,6 +5,7 @@ import ac.mdiq.podcini.databinding.BugReportBinding import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme import ac.mdiq.podcini.preferences.UserPreferences.getDataFolder import ac.mdiq.podcini.util.IntentUtils.openInBrowser +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.error.CrashReportWriter import android.content.ClipData import android.content.ClipboardManager @@ -43,7 +44,7 @@ class BugReportActivity : AppCompatActivity() { try { val crashFile = CrashReportWriter.file if (crashFile.exists()) stacktrace = IOUtils.toString(FileInputStream(crashFile), Charset.forName("UTF-8")) - else Log.d(TAG, stacktrace) + else Logd(TAG, stacktrace) } catch (e: IOException) { e.printStackTrace() diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt index 4daed4ef..e94aba42 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt @@ -277,8 +277,14 @@ class MainActivity : CastEnabledActivity() { override fun onStateChanged(view: View, state: Int) { Logd(TAG, "bottomSheet onStateChanged $state") when (state) { - BottomSheetBehavior.STATE_COLLAPSED -> onSlide(view,0.0f) + BottomSheetBehavior.STATE_COLLAPSED -> { + audioPlayerFragment.isCollapsed = true +// audioPlayerFragment.updateUi() + onSlide(view,0.0f) + } BottomSheetBehavior.STATE_EXPANDED -> { + audioPlayerFragment.isCollapsed = false +// audioPlayerFragment.updateUi() audioPlayerFragment.initDetailedView() onSlide(view, 1.0f) } 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 d19325af..e4dad2d1 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 @@ -28,10 +28,10 @@ import coil.imageLoader import coil.request.ErrorResult import coil.request.ImageRequest import coil.request.SuccessResult -import io.reactivex.Observable -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 // TODO: need to enable class SelectSubscriptionActivity : AppCompatActivity() { @@ -41,7 +41,8 @@ class SelectSubscriptionActivity : AppCompatActivity() { @Volatile private var listItems: List = listOf() - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null override fun onCreate(savedInstanceState: Bundle?) { setTheme(ThemeSwitcher.getTranslucentTheme(this)) @@ -100,22 +101,6 @@ class SelectSubscriptionActivity : AppCompatActivity() { private fun getBitmapFromUrl(feed: Feed) { val iconSize = (128 * resources.displayMetrics.density).toInt() - -// if (!feed.imageUrl.isNullOrBlank()) Glide.with(this) -// .asBitmap() -// .load(feed.imageUrl) -// .apply(RequestOptions.overrideOf(iconSize, iconSize)) -// .listener(object : RequestListener { -// @UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?, target: Target, isFirstResource: Boolean): Boolean { -// addShortcut(feed, null) -// return true -// } -// @UnstableApi override fun onResourceReady(resource: Bitmap, model: Any, target: Target, dataSource: DataSource, isFirstResource: Boolean): Boolean { -// addShortcut(feed, resource) -// return true -// } -// }).submit() - val request = ImageRequest.Builder(this) .data(feed.imageUrl) .setHeader("User-Agent", "Mozilla/5.0") @@ -134,24 +119,45 @@ class SelectSubscriptionActivity : AppCompatActivity() { } private fun loadSubscriptions() { - disposable?.dispose() +// 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 -> +// 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)) }) + + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) + getFeedItems(data.items, ArrayList()) + } + withContext(Dispatchers.Main) { 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) + val adapter: ArrayAdapter = ArrayAdapter(this@SelectSubscriptionActivity, R.layout.simple_list_item_multiple_choice_on_start, titles) binding.list.adapter = adapter - }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } + } override fun onDestroy() { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/CoverLoader.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/CoverLoader.kt index 5752f014..627251e4 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/CoverLoader.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/CoverLoader.kt @@ -2,12 +2,10 @@ package ac.mdiq.podcini.ui.adapter import ac.mdiq.podcini.R import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.util.Logd import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView import android.widget.TextView -import coil.Coil import coil.ImageLoader import coil.imageLoader import coil.request.ErrorResult diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/NavListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/NavListAdapter.kt index a2c4f000..d191d240 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/NavListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/NavListAdapter.kt @@ -6,12 +6,11 @@ import ac.mdiq.podcini.databinding.NavSectionItemBinding import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems -import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload -import ac.mdiq.podcini.preferences.UserPreferences.subscriptionsFilter import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.statistics.StatisticsFragment +import ac.mdiq.podcini.util.Logd import android.app.Activity import android.content.DialogInterface import android.content.Intent @@ -22,14 +21,12 @@ import android.view.ContextMenu.ContextMenuInfo import android.view.View.OnCreateContextMenuListener import android.widget.ImageView import android.widget.LinearLayout -import android.widget.RelativeLayout import android.widget.TextView import androidx.annotation.DrawableRes import androidx.annotation.OptIn import androidx.media3.common.util.UnstableApi import androidx.preference.PreferenceManager import androidx.recyclerview.widget.RecyclerView -import coil.load import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.apache.commons.lang3.ArrayUtils import java.lang.ref.WeakReference @@ -48,7 +45,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : private val titles: Array = context.resources.getStringArray(R.array.nav_drawer_titles) private val activity = WeakReference(context) @JvmField - var showSubscriptionList: Boolean = true + var showSubscriptionList: Boolean = false init { loadItems() @@ -65,16 +62,6 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : val newTags: MutableList = ArrayList(listOf(*NavDrawerFragment.NAV_DRAWER_TAGS)) val hiddenFragments = hiddenDrawerItems newTags.removeAll(hiddenFragments) - - if (newTags.contains(SUBSCRIPTION_LIST_TAG)) { - // we never want SUBSCRIPTION_LIST_TAG to be in 'tags' - // since it doesn't actually correspond to a position in the list, but is - // a placeholder that indicates if we should show the subscription list in the - // nav drawer at all. - showSubscriptionList = true - newTags.remove(SUBSCRIPTION_LIST_TAG) - } else showSubscriptionList = false - fragmentTags.clear() fragmentTags.addAll(newTags) notifyDataSetChanged() @@ -104,15 +91,12 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : } override fun getItemCount(): Int { - var baseCount = subscriptionOffset - if (showSubscriptionList) baseCount += itemAccess.count - return baseCount + return subscriptionOffset } override fun getItemId(position: Int): Long { val viewType = getItemViewType(position) return when (viewType) { - VIEW_TYPE_SUBSCRIPTION -> itemAccess.getItem(position - subscriptionOffset)?.id?:0 VIEW_TYPE_NAV -> (-abs(fragmentTags[position].hashCode().toLong().toDouble()) - 1).toLong() // Folder IDs are >0 else -> 0 } @@ -122,7 +106,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : return when { 0 <= position && position < fragmentTags.size -> VIEW_TYPE_NAV position < subscriptionOffset -> VIEW_TYPE_SECTION_DIVIDER - else -> VIEW_TYPE_SUBSCRIPTION + else -> 0 } } @@ -133,8 +117,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : val inflater = LayoutInflater.from(activity.get()) return when (viewType) { VIEW_TYPE_NAV -> NavHolder(inflater.inflate(R.layout.nav_listitem, parent, false)) - VIEW_TYPE_SECTION_DIVIDER -> DividerHolder(inflater.inflate(R.layout.nav_section_item, parent, false)) - else -> FeedHolder(inflater.inflate(R.layout.nav_listitem, parent, false)) + else -> DividerHolder(inflater.inflate(R.layout.nav_section_item, parent, false)) } } @@ -144,16 +127,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : holder.itemView.setOnCreateContextMenuListener(null) when (viewType) { VIEW_TYPE_NAV -> bindNavView(getLabel(fragmentTags[position]), position, holder as NavHolder) - VIEW_TYPE_SECTION_DIVIDER -> bindSectionDivider(holder as DividerHolder) - else -> { - val itemPos = position - subscriptionOffset - val item = itemAccess.getItem(itemPos) - if (item != null) { - bindListItem(item, holder as FeedHolder) - bindFeedView(item, holder) - } - holder.itemView.setOnCreateContextMenuListener(itemAccess) - } + else -> bindSectionDivider(holder as DividerHolder) } if (viewType != VIEW_TYPE_SECTION_DIVIDER) { holder.itemView.isSelected = itemAccess.isSelected(position) @@ -179,32 +153,34 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : holder.count.isClickable = false val tag = fragmentTags[position] - when { - tag == QueueFragment.TAG -> { + when (tag) { + SubscriptionFragment.TAG -> { + val sum = itemAccess.numberOfFeeds + if (sum > 0) { + holder.count.text = NumberFormat.getInstance().format(sum.toLong()) + holder.count.visibility = View.VISIBLE + } + } + QueueFragment.TAG -> { val queueSize = itemAccess.queueSize if (queueSize > 0) { holder.count.text = NumberFormat.getInstance().format(queueSize.toLong()) holder.count.visibility = View.VISIBLE } } -// tag == InboxFragment.TAG -> { -// val unreadItems = itemAccess.numberOfNewItems -// if (unreadItems > 0) { -// holder.count.text = NumberFormat.getInstance().format(unreadItems.toLong()) -// holder.count.visibility = View.VISIBLE -// } -// } - tag == SubscriptionFragment.TAG -> { - val sum = itemAccess.feedCounterSum - if (sum > 0) { - holder.count.text = NumberFormat.getInstance().format(sum.toLong()) + AllEpisodesFragment.TAG -> { + val numEpisodes = itemAccess.numberOfItems + if (numEpisodes > 0) { + holder.count.text = NumberFormat.getInstance().format(numEpisodes.toLong()) holder.count.visibility = View.VISIBLE } } - tag == DownloadsFragment.TAG && isEnableAutodownload -> { + DownloadsFragment.TAG -> { val epCacheSize = episodeCacheSize // don't count episodes that can be reclaimed val spaceUsed = (itemAccess.numberOfDownloadedItems - itemAccess.reclaimableItems) + holder.count.text = NumberFormat.getInstance().format(spaceUsed.toLong()) + holder.count.visibility = View.VISIBLE if (epCacheSize in 1..spaceUsed) { holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_alert, 0) holder.count.visibility = View.VISIBLE @@ -224,72 +200,15 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : } } + Logd("NavListAdapter", "bindNavView getting drawable for: ${fragmentTags[position]}") holder.image.setImageResource(getDrawable(fragmentTags[position])) } private fun bindSectionDivider(holder: DividerHolder) { -// val context = activity.get() ?: return - - if (subscriptionsFilter.isEnabled && showSubscriptionList) { - holder.itemView.isEnabled = true - holder.feedsFilteredMsg.visibility = View.VISIBLE - } else { - holder.itemView.isEnabled = false - holder.feedsFilteredMsg.visibility = View.GONE - } + holder.itemView.isEnabled = false + holder.feedsFilteredMsg.visibility = View.GONE } - private fun bindListItem(item: FeedDrawerItem, holder: FeedHolder) { - if (item.counter > 0) { - holder.count.visibility = View.VISIBLE - holder.count.text = NumberFormat.getInstance().format(item.counter.toLong()) - } else holder.count.visibility = View.GONE - - holder.title.text = item.title - val padding = (activity.get()!!.resources.getDimension(R.dimen.thumbnail_length_navlist) / 2).toInt() - holder.itemView.setPadding(item.layer * padding, 0, 0, 0) - } - - private fun bindFeedView(drawerItem: FeedDrawerItem, holder: FeedHolder) { - val feed = drawerItem.feed - val context = activity.get() ?: return - -// if (!feed.imageUrl.isNullOrBlank()) Glide.with(context) -// .load(feed.imageUrl) -// .apply(RequestOptions() -// .placeholder(R.color.light_gray) -// .error(R.color.light_gray) -// .transform(FitCenter(), -// RoundedCorners((4 * context.resources.displayMetrics.density).toInt())) -// .dontAnimate()) -// .into(holder.image) - - holder.image.load(feed.imageUrl) { - placeholder(R.color.light_gray) - error(R.mipmap.ic_launcher) - } - - if (feed.hasLastUpdateFailed()) { - val p = holder.title.layoutParams as RelativeLayout.LayoutParams - p.addRule(RelativeLayout.LEFT_OF, R.id.itxtvFailure) - holder.failure.visibility = View.VISIBLE - } else { - val p = holder.title.layoutParams as RelativeLayout.LayoutParams - p.addRule(RelativeLayout.LEFT_OF, R.id.txtvCount) - holder.failure.visibility = View.GONE - } - } - -// private fun bindTagView(tag: TagDrawerItem, holder: FeedHolder) { -// val context = activity.get() ?: return -// if (tag.isOpen) { -// holder.count.visibility = View.GONE -// } -// Glide.with(context).clear(holder.image) -// holder.image.setImageResource(R.drawable.ic_tag) -// holder.failure.visibility = View.GONE -// } - open class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) internal class DividerHolder(itemView: View) : Holder(itemView) { @@ -304,14 +223,6 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : val count: TextView = binding.txtvCount } - internal class FeedHolder(itemView: View) : Holder(itemView) { - val binding = NavListitemBinding.bind(itemView) - val image: ImageView = binding.imgvCover - val title: TextView = binding.txtvTitle - val failure: ImageView = binding.itxtvFailure - val count: TextView = binding.txtvCount - } - interface ItemAccess : OnCreateContextMenuListener { val count: Int @@ -323,12 +234,16 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : val numberOfNewItems: Int + val numberOfItems: Int + val numberOfDownloadedItems: Int val reclaimableItems: Int val feedCounterSum: Int + val numberOfFeeds: Int + fun onItemClick(position: Int) fun onItemLongClick(position: Int): Boolean @@ -339,12 +254,5 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) : companion object { const val VIEW_TYPE_NAV: Int = 0 const val VIEW_TYPE_SECTION_DIVIDER: Int = 1 - private const val VIEW_TYPE_SUBSCRIPTION = 2 - - /** - * a tag used as a placeholder to indicate if the subscription list should be displayed or not - * This tag doesn't correspond to any specific activity. - */ - const val SUBSCRIPTION_LIST_TAG: String = "SubscriptionList" } } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt index c42bd814..25b709a4 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt @@ -12,6 +12,7 @@ import ac.mdiq.podcini.R import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder +import ac.mdiq.podcini.util.Logd /** * List adapter for the queue. @@ -45,7 +46,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct val isLtr = holder.itemView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR val factor = (if (isLtr) 1 else -1).toFloat() if (factor * event.x < factor * 0.5 * v1.width) swipeActions.startDrag(holder) - else Log.d(TAG, "Ignoring drag in right half of the image") + else Logd(TAG, "Ignoring drag in right half of the image") } false } 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 fa1ea5f6..4c0e49fe 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 @@ -10,9 +10,10 @@ import android.content.DialogInterface import android.util.Log import androidx.annotation.OptIn import androidx.media3.common.util.UnstableApi -import io.reactivex.Completable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext object RemoveFeedDialog { private const val TAG = "RemoveFeedDialog" @@ -41,21 +42,39 @@ 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( - { +// 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 { + withContext(Dispatchers.IO) { + for (feed in feeds) { + DBWriter.deleteFeed(context, feed.id).get() + } + } + withContext(Dispatchers.Main) { Logd(TAG, "Feed(s) deleted") progressDialog.dismiss() - }, { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - progressDialog.dismiss() - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + withContext(Dispatchers.Main) { progressDialog.dismiss() } + } + } } } dialog.createNewDialog().show() 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 6c5ef6dd..d64ea332 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 @@ -19,9 +19,10 @@ import androidx.fragment.app.DialogFragment import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.GridLayoutManager import com.google.android.material.dialog.MaterialAlertDialogBuilder -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import java.io.Serializable @@ -87,18 +88,33 @@ class TagSettingsDialog : DialogFragment() { } private fun loadTags() { - Observable.fromCallable { - DBReader.getTags() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { result: List -> +// 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 { + val result = withContext(Dispatchers.IO) { + DBReader.getTags() + } + withContext(Dispatchers.Main) { val acAdapter = ArrayAdapter(requireContext(), R.layout.single_tag_text_view, result) binding.newTagEditText.setAdapter(acAdapter) - }, { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } } private fun addTag(name: String) { 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 e2eabe44..0433b877 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 @@ -10,6 +10,7 @@ import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.OpmlImportActivity +import ac.mdiq.podcini.util.Logd import android.content.* import android.net.Uri import android.os.Bundle @@ -26,9 +27,10 @@ import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext /** * Provides actions for adding new podcast subscriptions. @@ -52,7 +54,7 @@ class AddFeedFragment : Fragment() { _binding = AddfeedBinding.inflate(inflater) activity = getActivity() as? MainActivity - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") displayUpArrow = parentFragmentManager.backStackEntryCount != 0 if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW) @@ -162,18 +164,36 @@ 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) - }) +// 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 { + val feed = withContext(Dispatchers.IO) { + addLocalFolder(uri) + } + withContext(Dispatchers.Main) { + if (feed != null) { + val fragment: Fragment = FeedItemlistFragment.newInstance(feed.id) + (getActivity() as MainActivity).loadChildFragment(fragment) + } + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + (getActivity() as MainActivity).showSnackbarAbovePlayer(e.localizedMessage, Snackbar.LENGTH_LONG) + } + } } @UnstableApi private fun addLocalFolder(uri: Uri): Feed? { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt index 427b35ca..1f70bc39 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AllEpisodesFragment.kt @@ -10,9 +10,9 @@ import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent import ac.mdiq.podcini.ui.dialog.ItemSortDialog +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.FeedListUpdateEvent import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.MenuItem import android.view.View @@ -27,9 +27,10 @@ import org.greenrobot.eventbus.Subscribe * Shows all episodes (possibly filtered by user). */ class AllEpisodesFragment : BaseEpisodesListFragment() { + @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val root = super.onCreateView(inflater, container, savedInstanceState) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar.inflateMenu(R.menu.episodes) toolbar.setTitle(R.string.episodes_label) @@ -128,7 +129,7 @@ class AllEpisodesFragment : BaseEpisodesListFragment() { } companion object { - const val TAG: String = "EpisodesFragment" + const val TAG: String = "AllEpisodesFragment" const val PREF_NAME: String = "PrefAllEpisodesFragment" } } 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 881c0c6b..c79d4d3c 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 @@ -64,11 +64,8 @@ import coil.request.ImageRequest import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.elevation.SurfaceColors -import io.reactivex.Maybe -import io.reactivex.MaybeEmitter -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -97,8 +94,9 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar private lateinit var cardViewSeek: CardView private lateinit var txtvSeek: TextView + val scope = CoroutineScope(Dispatchers.Main) private var controller: PlaybackController? = null - private var disposable: Disposable? = null +// private var disposable: Disposable? = null private var seekedToChapterStart = false private var currentChapterIndex = -1 private var duration = 0 @@ -106,6 +104,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar private var currentMedia: Playable? = null private var currentitem: FeedItem? = null + var isCollapsed = true + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) _binding = AudioplayerFragmentBinding.inflate(inflater) @@ -163,6 +163,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar _binding = null controller?.release() controller = null + scope.cancel() EventBus.getDefault().unregister(this) Logd(TAG, "Fragment destroyed") } @@ -192,32 +193,55 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar (activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED } - private fun loadMediaInfo(includingChapters: Boolean) { - Logd(TAG, "loadMediaInfo called") +// 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) { 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() + if (currentMedia == null || theMedia.getIdentifier() != currentMedia?.getIdentifier() || (includingChapters && !theMedia.chaptersLoaded())) { + Logd(TAG, "loadMediaInfo loading details ${theMedia.getIdentifier()} chapter: $includingChapters") + scope.launch { + val media: Playable = withContext(Dispatchers.IO) { + theMedia.apply { + if (includingChapters) ChapterUtils.loadChapters(this, requireContext(), false) + } + } + currentMedia = media + updateUi() + playerFragment1?.updateUi(currentMedia) + playerFragment2?.updateUi(currentMedia) + if (!includingChapters) loadMediaInfo(true) + }.invokeOnCompletion { throwable -> + if (throwable!= null) { + Log.e(TAG, Log.getStackTraceString(throwable)) + } } - .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) }) } } @@ -240,10 +264,10 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar } } - private fun updateUi(media: Playable?) { + fun updateUi() { Logd(TAG, "updateUi called") - setChapterDividers(media) - setupOptionsMenu(media) + setChapterDividers(currentMedia) + setupOptionsMenu(currentMedia) } @Subscribe(threadMode = ThreadMode.MAIN) @@ -265,7 +289,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar override fun onStop() { super.onStop() // progressIndicator.visibility = View.GONE // Controller released; we will not receive buffering updates - disposable?.dispose() +// disposable?.dispose() } // @Subscribe(threadMode = ThreadMode.MAIN) @@ -328,7 +352,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: ("\n${Converter.getDurationStringLong(position)}") } else txtvSeek.text = Converter.getDurationStringLong(position) } - duration != controller!!.duration -> updateUi(controller!!.getMedia()) + duration != controller!!.duration -> updateUi() } } @@ -695,7 +719,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar (activity as MainActivity).setPlayerVisible(true) onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration())) - val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media) + "sdfsdf" + val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media) val imgLocFB = ImageResourceUtils.getFallbackImageLocation(media) val imageLoader = imgvCover.context.imageLoader val imageRequest = ImageRequest.Builder(requireContext()) 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 db0a31a7..e5101c46 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 @@ -39,11 +39,7 @@ import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.snackbar.Snackbar import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Completable -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -62,6 +58,8 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect var _binding: BaseEpisodesListFragmentBinding? = null protected val binding get() = _binding!! + val scope = CoroutineScope(Dispatchers.Main) + lateinit var recyclerView: EpisodeItemListRecyclerView lateinit var emptyView: EmptyViewHandler lateinit var speedDialView: SpeedDialView @@ -77,7 +75,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect @JvmField var episodes: MutableList = ArrayList() - protected var disposable: Disposable? = null +// protected var disposable: Disposable? = null @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) @@ -202,7 +200,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect override fun onStop() { super.onStop() - disposable?.dispose() +// disposable?.dispose() } @UnstableApi override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -242,68 +240,115 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect @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) +// 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)) }) + + scope.launch { + try { + withContext(Dispatchers.IO) { + 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) + } + withContext(Dispatchers.Main) { + listAdapter.endSelectMode() + } + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ listAdapter.endSelectMode() }, - { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) } private fun setupLoadMoreScrollListener() { recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(view: RecyclerView, deltaX: Int, deltaY: Int) { super.onScrolled(view, deltaX, deltaY) + Logd(TAG, "addOnScrollListener called isLoadingMore:$isLoadingMore hasMoreItems:$hasMoreItems ${recyclerView.isScrolledToBottom}") if (!isLoadingMore && hasMoreItems && recyclerView.isScrolledToBottom) { /* The end of the list has been reached. Load more data. */ page++ loadMoreItems() - isLoadingMore = true +// isLoadingMore = true } } }) } private fun loadMoreItems() { - disposable?.dispose() +// disposable?.dispose() + Logd(TAG, "loadMoreItems() called $page") 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) +// 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 } +// }) - }, { error: Throwable? -> + scope.launch { + try { + val data = withContext(Dispatchers.IO) { + loadMoreData(page) + } + withContext(Dispatchers.Main) { + 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) + } + } catch (e: 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 } - }) + Log.e(TAG, Log.getStackTraceString(e)) + } finally { + withContext(Dispatchers.Main) { recyclerView.post { isLoadingMore = false } } + } + } } override fun onDestroyView() { super.onDestroyView() _binding = null + scope.cancel() EventBus.getDefault().unregister(this) listAdapter.endSelectMode() } @@ -319,7 +364,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect @Subscribe(threadMode = ThreadMode.MAIN) fun onEventMainThread(event: FeedItemEvent) { - Log.d(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]") + Logd(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]") for (item in event.items) { val pos: Int = FeedItemUtil.indexOfItemWithId(episodes, item.id) if (pos >= 0) { @@ -338,7 +383,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem) currentPlaying!!.notifyPlaybackPositionUpdated(event) else { - Log.d(TAG, "onEventMainThread() search list") + Logd(TAG, "onEventMainThread() search list") for (i in 0 until listAdapter.itemCount) { val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder if (holder != null && holder.isCurrentlyPlayingItem) { @@ -395,15 +440,37 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect } fun loadItems() { - disposable?.dispose() + Logd(TAG, "loadItems() called") +// disposable?.dispose() - disposable = Observable.fromCallable { - Pair(loadData().toMutableList(), loadTotalItemCount()) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { data: Pair, Int> -> +// 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)) +// }) + + scope.launch { + try { + val data = withContext(Dispatchers.IO) { + Pair(loadData().toMutableList(), loadTotalItemCount()) + } + withContext(Dispatchers.Main) { val restoreScrollPosition = episodes.isEmpty() episodes = data.first hasMoreItems = !(page == 1 && episodes.size < EPISODES_PER_PAGE) @@ -413,11 +480,14 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect listAdapter.setTotalNumberOfItems(data.second) if (restoreScrollPosition) recyclerView.restoreScrollPosition(getPrefName()) updateToolbar() - }, { error: Throwable? -> - listAdapter.setDummyViews(0) - listAdapter.updateItems(emptyList()) - Log.e(TAG, Log.getStackTraceString(error)) - }) + } + } catch (e: Throwable) { + listAdapter.setDummyViews(0) + listAdapter.updateItems(emptyList()) + Log.e(TAG, Log.getStackTraceString(e)) + } + } + } protected abstract fun loadData(): List @@ -446,7 +516,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect } companion object { - const val TAG: String = "EpisodesListFragment" + const val TAG: String = "BaseEpisodesListFragment" private const val KEY_UP_ARROW = "up_arrow" const val EPISODES_PER_PAGE: Int = 150 } 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 0187ebd3..8a398c06 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 @@ -9,6 +9,7 @@ import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.OnlineFeedsAdapter +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.DiscoveryDefaultUpdateEvent import android.content.Context import android.content.DialogInterface @@ -29,10 +30,7 @@ import androidx.media3.common.util.UnstableApi import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.MaterialAutoCompleteTextView -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import java.util.* @@ -61,7 +59,9 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { */ private var searchResults: List? = null private var topList: List? = null - private var disposable: Disposable? = null + + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null private var countryCode: String? = "US" private var hidden = false private var needsConfirm = false @@ -101,7 +101,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { _binding = FragmentItunesSearchBinding.inflate(inflater) // val root = inflater.inflate(R.layout.fragment_itunes_search, container, false) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") gridView = binding.gridView adapter = OnlineFeedsAdapter(requireActivity(), ArrayList()) gridView.setAdapter(adapter) @@ -135,13 +135,14 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { override fun onDestroy() { super.onDestroy() _binding = null - disposable?.dispose() + scope.cancel() +// disposable?.dispose() adapter = null } private fun loadToplist(country: String?) { - disposable?.dispose() +// disposable?.dispose() gridView.visibility = View.GONE txtvError.visibility = View.GONE @@ -175,23 +176,44 @@ 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? -> +// 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 +// }) + + scope.launch { + try { + val podcasts = withContext(Dispatchers.IO) { + loader.loadToplist(country?:"", NUM_OF_TOP_PODCASTS, DBReader.getFeedList()) + } + withContext(Dispatchers.Main) { 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 - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + progressBar.visibility = View.GONE + txtvError.text = e.message + txtvError.visibility = View.VISIBLE + butRetry.setOnClickListener { loadToplist(country) } + butRetry.visibility = View.VISIBLE + } + } + } override fun onMenuItemClick(item: MenuItem): Boolean { 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 147a8f80..8f50e93c 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 @@ -8,6 +8,7 @@ import ac.mdiq.podcini.storage.model.download.DownloadResult import ac.mdiq.podcini.ui.adapter.DownloadLogAdapter import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog import ac.mdiq.podcini.ui.view.EmptyViewHandler +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.DownloadLogEvent import android.os.Bundle import android.util.Log @@ -17,10 +18,7 @@ import android.widget.AdapterView.OnItemClickListener import androidx.appcompat.widget.Toolbar import androidx.media3.common.util.UnstableApi import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -34,15 +32,17 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To private lateinit var adapter: DownloadLogAdapter private var downloadLog: List = ArrayList() - private var disposable: Disposable? = null +// private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) override fun onStop() { super.onStop() - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") _binding = DownloadLogFragmentBinding.inflate(inflater) binding.toolbar.inflateMenu(R.menu.download_log) binding.toolbar.setOnMenuItemClickListener(this) @@ -95,17 +95,31 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To } private fun loadDownloadLog() { - disposable?.dispose() +// disposable?.dispose() - disposable = Observable.fromCallable { DBReader.getDownloadLog() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result: List? -> - if (result != null) { +// 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)) }) + + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + DBReader.getDownloadLog() + } + withContext(Dispatchers.Main) { downloadLog = result adapter.setDownloadLog(downloadLog) } - }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } } companion object { 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 1d75cdd4..952497dc 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 @@ -41,10 +41,10 @@ import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.snackbar.Snackbar import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Observable -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 org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -69,7 +69,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To private lateinit var progressBar: ProgressBar private lateinit var emptyView: EmptyViewHandler - private var disposable: Disposable? = null +// private var disposable: Disposable? = null private var displayUpArrow = false private var currentPlaying: EpisodeItemViewHolder? = null @@ -166,7 +166,7 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To adapter.endSelectMode() toolbar.setOnMenuItemClickListener(null) toolbar.setOnLongClickListener(null) - disposable?.dispose() +// disposable?.dispose() super.onDestroyView() } @@ -303,39 +303,72 @@ class DownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, To } private fun loadItems() { - disposable?.dispose() +// 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) +// 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 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 -> + val scope = CoroutineScope(Dispatchers.Main) + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + 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()) { + downloadedItems + } else { + for (url in runningDownloads) { + if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) continue + mediaUrls.add(url) + } + val currentDownloads: MutableList = DBReader.getFeedItemsWithUrl(mediaUrls).toMutableList() + currentDownloads.addAll(downloadedItems) + currentDownloads + } + } + withContext(Dispatchers.Main) { 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)) - }) + } + } catch (e: Throwable) { + adapter.setDummyViews(0) + adapter.updateItems(emptyList()) + Log.e(TAG, Log.getStackTraceString(e)) + } + } } private fun refreshInfoBar() { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt index 48700ec3..a468a52b 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeHomeFragment.kt @@ -5,6 +5,7 @@ import ac.mdiq.podcini.databinding.EpisodeHomeFragmentBinding import ac.mdiq.podcini.storage.DBWriter.persistFeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.ui.utils.ShownotesCleaner +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.NetworkUtils.fetchHtmlSource import android.content.Context import android.os.Bundle @@ -57,7 +58,7 @@ class EpisodeHomeFragment : Fragment() { super.onCreateView(inflater, container, savedInstanceState) _binding = EpisodeHomeFragmentBinding.inflate(inflater, container, false) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar = binding.toolbar toolbar.title = "" @@ -74,7 +75,7 @@ class EpisodeHomeFragment : Fragment() { webViewClient = object : WebViewClient() { override fun onPageFinished(view: WebView?, url: String?) { val isEmpty = view?.title.isNullOrEmpty() && view?.contentDescription.isNullOrEmpty() - if (isEmpty) Log.d(TAG, "content is empty") + if (isEmpty) Logd(TAG, "content is empty") } } } @@ -127,7 +128,7 @@ class EpisodeHomeFragment : Fragment() { } private fun initializeTTS(context: Context) { - Log.d(TAG, "starting TTS") + Logd(TAG, "starting TTS") if (tts == null) { tts = TextToSpeech(context) { status: Int -> if (status == TextToSpeech.SUCCESS) { @@ -142,7 +143,7 @@ class EpisodeHomeFragment : Fragment() { } ttsReady = true // semaphore.release() - Log.d(TAG, "TTS init success") + Logd(TAG, "TTS init success") } else { Log.w(TAG, "TTS init failed") requireActivity().runOnUiThread {Toast.makeText(context, R.string.tts_init_failed, Toast.LENGTH_LONG).show() } @@ -154,7 +155,7 @@ class EpisodeHomeFragment : Fragment() { private fun showWebContent() { if (!currentItem?.link.isNullOrEmpty()) { binding.webView.settings.javaScriptEnabled = jsEnabled - Log.d(TAG, "currentItem!!.link ${currentItem!!.link}") + Logd(TAG, "currentItem!!.link ${currentItem!!.link}") binding.webView.loadUrl(currentItem!!.link!!) binding.readerView.visibility = View.GONE binding.webView.visibility = View.VISIBLE @@ -169,7 +170,7 @@ class EpisodeHomeFragment : Fragment() { private val menuProvider = object: MenuProvider { override fun onPrepareMenu(menu: Menu) { // super.onPrepareMenu(menu) - Log.d(TAG, "onPrepareMenu called") + Logd(TAG, "onPrepareMenu called") val textSpeech = menu.findItem(R.id.text_speech) textSpeech?.isVisible = readMode && tts != null if (textSpeech?.isVisible == true) { @@ -187,18 +188,18 @@ class EpisodeHomeFragment : Fragment() { @OptIn(UnstableApi::class) override fun onMenuItemSelected(menuItem: MenuItem): Boolean { when (menuItem.itemId) { R.id.switch_home -> { - Log.d(TAG, "switch_home selected") + Logd(TAG, "switch_home selected") switchMode() return true } R.id.switchJS -> { - Log.d(TAG, "switchJS selected") + Logd(TAG, "switchJS selected") jsEnabled = !jsEnabled showWebContent() return true } R.id.text_speech -> { - Log.d(TAG, "text_speech selected: $readerText") + Logd(TAG, "text_speech selected: $readerText") if (tts != null) { if (tts!!.isSpeaking) tts?.stop() if (!ttsPlaying) { @@ -255,7 +256,7 @@ class EpisodeHomeFragment : Fragment() { @OptIn(UnstableApi::class) override fun onDestroyView() { super.onDestroyView() - Log.d(TAG, "onDestroyView") + Logd(TAG, "onDestroyView") cleatWebview(binding.webView) cleatWebview(binding.readerView) _binding = null @@ -267,7 +268,7 @@ class EpisodeHomeFragment : Fragment() { @UnstableApi private fun updateAppearance() { if (currentItem == null) { - Log.d(TAG, "updateAppearance currentItem is null") + Logd(TAG, "updateAppearance currentItem is null") return } // onPrepareOptionsMenu(toolbar.menu) @@ -284,7 +285,7 @@ class EpisodeHomeFragment : Fragment() { @JvmStatic fun newInstance(item: FeedItem): EpisodeHomeFragment { val fragment = EpisodeHomeFragment() - Log.d(TAG, "item.itemIdentifier ${item.itemIdentifier}") + Logd(TAG, "item.itemIdentifier ${item.itemIdentifier}") if (item.itemIdentifier != currentItem?.itemIdentifier) { currentItem = item } else { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt index e7f41828..f6f61867 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt @@ -51,10 +51,10 @@ import com.skydoves.balloon.ArrowOrientation import com.skydoves.balloon.ArrowOrientationRules import com.skydoves.balloon.Balloon import com.skydoves.balloon.BalloonAnimation -import io.reactivex.Observable -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 org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -93,7 +93,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { private var actionButton1: ItemActionButton? = null private var actionButton2: ItemActionButton? = null - private var disposable: Disposable? = null +// private var disposable: Disposable? = null private var controller: PlaybackController? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -258,7 +258,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { _binding = null EventBus.getDefault().unregister(this) controller?.release() - disposable?.dispose() +// disposable?.dispose() root.removeView(webvDescription) webvDescription.clearHistory() webvDescription.clearCache(true) @@ -411,33 +411,51 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { load() } +// @UnstableApi private fun load0() { +// disposable?.dispose() +// if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE +// +// Logd(TAG, "load() called") +// disposable = Observable.fromCallable { this.loadInBackground() } +// .subscribeOn(Schedulers.io()) +// .observeOn(AndroidSchedulers.mainThread()) +// .subscribe({ result: FeedItem? -> +// progbarLoading.visibility = View.GONE +// item = result +// onFragmentLoaded() +// itemsLoaded = true +// }, +// { error: Throwable? -> +// Log.e(TAG, Log.getStackTraceString(error)) +// }) +// } + @UnstableApi private fun load() { - disposable?.dispose() if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE Logd(TAG, "load() called") - disposable = Observable.fromCallable { this.loadInBackground() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result: FeedItem? -> - progbarLoading.visibility = View.GONE - item = result - onFragmentLoaded() - itemsLoaded = true - }, - { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - }) - } - - private fun loadInBackground(): FeedItem? { - val feedItem = item - if (feedItem != null) { - val duration = feedItem.media?.getDuration()?: Int.MAX_VALUE - DBReader.loadTextDetailsOfFeedItem(feedItem) - webviewData = ShownotesCleaner(requireContext(), feedItem.description?:"", duration).processShownotes() + val scope = CoroutineScope(Dispatchers.Main) + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + val feedItem = item + if (feedItem != null) { + val duration = feedItem.media?.getDuration()?: Int.MAX_VALUE + if (feedItem.description == null || feedItem.transcript == null) DBReader.loadTextDetailsOfFeedItem(feedItem) + webviewData = ShownotesCleaner(requireContext(), feedItem.description?:"", duration).processShownotes() + } + feedItem + } + withContext(Dispatchers.Main) { + progbarLoading.visibility = View.GONE + item = result + onFragmentLoaded() + itemsLoaded = true + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } } - return feedItem } fun setItem(item_: FeedItem) { @@ -446,15 +464,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { companion object { private const val TAG = "EpisodeInfoFragment" - private const val ARG_FEEDITEM = "feeditem" @JvmStatic fun newInstance(item: FeedItem): EpisodeInfoFragment { val fragment = EpisodeInfoFragment() fragment.setItem(item) -// val args = Bundle() -// args.putSerializable(ARG_FEEDITEM, item) -// fragment.arguments = args return fragment } } 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 3fbafc1e..0874ec38 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 @@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.fragment import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.FeedinfoBinding import ac.mdiq.podcini.net.discovery.CombinedSearcher -import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBTasks import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.FeedFunding @@ -13,6 +12,7 @@ import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.feed.FeedStatisticsFragment import ac.mdiq.podcini.ui.view.ToolbarIconTintManager import ac.mdiq.podcini.util.IntentUtils +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.ShareUtils import ac.mdiq.podcini.util.syndication.HtmlToPlainText import android.R.string @@ -44,12 +44,10 @@ import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import io.reactivex.Completable -import io.reactivex.Maybe -import io.reactivex.MaybeEmitter -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 org.apache.commons.lang3.StringUtils /** @@ -57,14 +55,10 @@ import org.apache.commons.lang3.StringUtils */ @UnstableApi class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { - private val addLocalFolderLauncher = registerForActivityResult(AddLocalFolder()) { uri: Uri? -> this.addLocalFolderResult(uri) } - private var _binding: FeedinfoBinding? = null private val binding get() = _binding!! private var feed: Feed? = null - private var disposable: Disposable? = null - private lateinit var imgvCover: ImageView private lateinit var txtvTitle: TextView private lateinit var txtvDescription: TextView @@ -77,6 +71,10 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { private lateinit var header: View private lateinit var toolbar: MaterialToolbar + private val addLocalFolderLauncher = registerForActivityResult(AddLocalFolder()) { + uri: Uri? -> this.addLocalFolderResult(uri) + } + private val copyUrlToClipboard = View.OnClickListener { if (feed != null && feed!!.download_url != null) { val url: String = feed!!.download_url!! @@ -90,7 +88,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FeedinfoBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar = binding.toolbar toolbar.title = "" toolbar.inflateMenu(R.menu.feedinfo) @@ -134,7 +132,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { txtvUrl.setOnClickListener(copyUrlToClipboard) - val feedId = requireArguments().getLong(EXTRA_FEED_ID) +// val feedId = requireArguments().getLong(EXTRA_FEED_ID) + val feedId = feed!!.id parentFragmentManager.beginTransaction().replace(R.id.statisticsFragmentContainer, FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment") .commitAllowingStateLoss() @@ -144,24 +143,10 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { (activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE) } + showFeed() return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - val feedId = requireArguments().getLong(EXTRA_FEED_ID) - disposable = Maybe.create { emitter: MaybeEmitter -> - val feed: Feed? = DBReader.getFeed(feedId) - if (feed != null) emitter.onSuccess(feed) - else emitter.onComplete() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result: Feed? -> - feed = result - showFeed() - }, { error: Throwable? -> Log.d(TAG, Log.getStackTraceString(error)) }, {}) - } - override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) val horizontalSpacing = resources.getDimension(R.dimen.additional_horizontal_spacing).toInt() @@ -171,28 +156,11 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { private fun showFeed() { if (feed == null) return - Log.d(TAG, "Language is " + feed!!.language) - Log.d(TAG, "Author is " + feed!!.author) - Log.d(TAG, "URL is " + feed!!.download_url) -// if (!feed?.imageUrl.isNullOrBlank()) { -// Glide.with(this) -// .load(feed!!.imageUrl) -// .apply(RequestOptions() -// .placeholder(R.color.light_gray) -// .error(R.color.light_gray) -// .fitCenter() -// .dontAnimate()) -// .into(imgvCover) -// Glide.with(this) -// .load(feed!!.imageUrl) -// .apply(RequestOptions() -// .placeholder(R.color.image_readability_tint) -// .error(R.color.image_readability_tint) -// .transform(FastBlurTransformation()) -// .dontAnimate()) -// .into(imgvBackground) -// } + Logd(TAG, "Language is " + feed!!.language) + Logd(TAG, "Author is " + feed!!.author) + Logd(TAG, "URL is " + feed!!.download_url) +// TODO: need to generate blurred image for background imgvCover.load(feed!!.imageUrl) { placeholder(R.color.light_gray) error(R.mipmap.ic_launcher) @@ -244,18 +212,21 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { refreshToolbarState() } + fun setFeed(feed_: Feed) { + feed = feed_ + } + override fun onDestroyView() { super.onDestroyView() _binding = null - disposable?.dispose() + feed = null } private fun refreshToolbarState() { toolbar.menu?.findItem(R.id.reconnect_local_folder)?.setVisible(feed != null && feed!!.isLocalFeed) toolbar.menu?.findItem(R.id.share_item)?.setVisible(feed != null && !feed!!.isLocalFeed) toolbar.menu?.findItem(R.id.visit_website_item) - ?.setVisible(feed != null && feed!!.link != null && IntentUtils.isCallable(requireContext(), - Intent(Intent.ACTION_VIEW, Uri.parse(feed!!.link)))) + ?.setVisible(feed != null && feed!!.link != null && IntentUtils.isCallable(requireContext(), Intent(Intent.ACTION_VIEW, Uri.parse(feed!!.link)))) toolbar.menu?.findItem(R.id.edit_feed_url_item)?.setVisible(feed != null && !feed!!.isLocalFeed) } @@ -302,18 +273,37 @@ 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) +// 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) + scope.launch { + try { + withContext(Dispatchers.IO) { + 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) + } + withContext(Dispatchers.Main) { + (activity as MainActivity).showSnackbarAbovePlayer(string.ok, Snackbar.LENGTH_SHORT) + } + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + (activity as MainActivity).showSnackbarAbovePlayer(e.localizedMessage, Snackbar.LENGTH_LONG) + } + } } - .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) }) } private class AddLocalFolder : ActivityResultContracts.OpenDocumentTree() { @@ -323,13 +313,11 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { } companion object { - private const val EXTRA_FEED_ID = "ac.mdiq.podcini.extra.feedId" private const val TAG = "FeedInfoActivity" + fun newInstance(feed: Feed): FeedInfoFragment { val fragment = FeedInfoFragment() - val arguments = Bundle() - arguments.putLong(EXTRA_FEED_ID, feed.id) - fragment.arguments = arguments + fragment.setFeed(feed) return fragment } } 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 eecc335e..10ae2286 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 @@ -47,19 +47,12 @@ import com.google.android.material.snackbar.Snackbar import com.joanzapata.iconify.Iconify import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Maybe -import io.reactivex.Observable -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.* import org.apache.commons.lang3.StringUtils import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.util.concurrent.Callable import java.util.concurrent.ExecutionException import java.util.concurrent.Semaphore @@ -84,9 +77,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba private var headerCreated = false private var feedID: Long = 0 private var feed: Feed? = null - private var disposable: Disposable? = null +// private var disposable: Disposable? = null private val ioScope = CoroutineScope(Dispatchers.IO) + private val scope = CoroutineScope(Dispatchers.Main) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -217,7 +211,9 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba _binding = null _speedDialBinding = null EventBus.getDefault().unregister(this) - disposable?.dispose() +// disposable?.dispose() + ioScope.cancel() + scope.cancel() adapter.endSelectMode() tts?.stop() @@ -483,20 +479,33 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba } private fun showErrorDetails() { - Maybe.fromCallable( - Callable { +// 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) }) + + scope.launch { + val downloadResult = withContext(Dispatchers.IO) { 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) }) + if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) null else feedDownloadLog[0] + } + withContext(Dispatchers.Main) { + if (downloadResult != null) DownloadLogDetailsDialog(requireContext(), downloadResult).show() + else DownloadLogFragment().show(childFragmentManager, null) + } + }.invokeOnCompletion { throwable -> + throwable?.printStackTrace() + } } @UnstableApi private fun showFeedInfo() { @@ -508,19 +517,6 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba private fun loadFeedImage() { if (!feed?.imageUrl.isNullOrBlank()) { -// binding.imgvBackground.load(feed!!.imageUrl) { -// placeholder(R.color.image_readability_tint) -// error(R.color.image_readability_tint) -// } -// Glide.with(this) -// .load(feed!!.imageUrl) -// .apply(RequestOptions() -// .placeholder(R.color.image_readability_tint) -// .error(R.color.image_readability_tint) -// .transform(FastBlurTransformation()) -// .dontAnimate()) -// .into(binding.imgvBackground) - binding.header.imgvCover.load(feed!!.imageUrl) { placeholder(R.color.light_gray) error(R.mipmap.ic_launcher) @@ -529,17 +525,54 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba } @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) { +// 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)) +// }) + + scope.launch { + try { + feed = withContext(Dispatchers.IO) { + val feed_ = loadData() + if (feed_ != null) { var hasNonMediaItems = false - for (item in feed!!.items) { + for (item in feed_!!.items) { if (item.media == null) { hasNonMediaItems = true break @@ -554,6 +587,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba } } } + feed_ + } + withContext(Dispatchers.Main) { + Logd(TAG, "loadItems subscribe called ${feed?.title}") swipeActions.setFilter(feed?.itemFilter) refreshHeaderView() binding.progressBar.visibility = View.GONE @@ -561,14 +598,16 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba 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)) - }) + } + } catch (e: Throwable) { + feed = null + refreshHeaderView() + adapter.setDummyViews(0) + adapter.updateItems(emptyList()) + updateToolbar() + Log.e(TAG, Log.getStackTraceString(e)) + } + } } private fun loadData(): Feed? { 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 f3151ded..b1371a79 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 @@ -16,6 +16,7 @@ import ac.mdiq.podcini.ui.dialog.AuthenticationDialog import ac.mdiq.podcini.ui.dialog.EpisodeFilterDialog import ac.mdiq.podcini.ui.dialog.FeedPreferenceSkipDialog import ac.mdiq.podcini.ui.dialog.TagSettingsDialog +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent @@ -40,12 +41,7 @@ import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreferenceCompat import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder -import io.reactivex.Maybe -import io.reactivex.MaybeEmitter -import io.reactivex.MaybeOnSubscribe -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import java.util.* import java.util.concurrent.ExecutionException @@ -54,13 +50,14 @@ class FeedSettingsFragment : Fragment() { private var _binding: FeedsettingsBinding? = null private val binding get() = _binding!! - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FeedsettingsBinding.inflate(inflater) val feedId = requireArguments().getLong(EXTRA_FEED_ID) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") val toolbar = binding.toolbar toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() } @@ -69,16 +66,31 @@ 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? -> Log.d(TAG, Log.getStackTraceString(error)) }, - {}) +// 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)) }, +// {}) + + scope.launch { + val feed = withContext(Dispatchers.IO) { + DBReader.getFeed(feedId) + } + if (feed!= null) { + withContext(Dispatchers.Main) { + toolbar.subtitle = feed.title + } + } + }.invokeOnCompletion { throwable -> + if (throwable!= null) { + Logd(TAG, Log.getStackTraceString(throwable)) + } + } return binding.root } @@ -86,12 +98,14 @@ class FeedSettingsFragment : Fragment() { override fun onDestroyView() { super.onDestroyView() _binding = null - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } class FeedSettingsPreferenceFragment : PreferenceFragmentCompat() { private var feed: Feed? = null - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null private var feedPreferences: FeedPreferences? = null var notificationPermissionDenied: Boolean = false @@ -123,46 +137,83 @@ 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 +// 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)) }, {}) - setupAutoDownloadGlobalPreference() - setupAutoDownloadPreference() - setupKeepUpdatedPreference() - setupAutoDeletePreference() - setupVolumeAdaptationPreferences() -// setupNewEpisodesAction() - setupAuthentificationPreference() - setupEpisodeFilterPreference() - setupPlaybackSpeedPreference() - setupFeedAutoSkipPreference() -// setupEpisodeNotificationPreference() - setupTags() + scope.launch { + feed = withContext(Dispatchers.IO) { + DBReader.getFeed(feedId) + } + if (feed!= null) { + withContext(Dispatchers.Main) { + feedPreferences = feed!!.preferences - updateAutoDeleteSummary() - updateVolumeAdaptationValue() - updateAutoDownloadEnabled() -// updateNewEpisodesAction() + setupAutoDownloadGlobalPreference() + setupAutoDownloadPreference() + setupKeepUpdatedPreference() + setupAutoDeletePreference() + setupVolumeAdaptationPreferences() + setupAuthentificationPreference() + setupEpisodeFilterPreference() + setupPlaybackSpeedPreference() + setupFeedAutoSkipPreference() + setupTags() - if (feed!!.isLocalFeed) { - findPreference(PREF_AUTHENTICATION)!!.isVisible = false - findPreference(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false + updateAutoDeleteSummary() + updateVolumeAdaptationValue() + updateAutoDownloadEnabled() + + if (feed!!.isLocalFeed) { + findPreference(PREF_AUTHENTICATION)!!.isVisible = false + findPreference(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false + } + findPreference(PREF_SCREEN)!!.isVisible = true } - findPreference(PREF_SCREEN)!!.isVisible = true - }, { error: Throwable? -> Log.d(TAG, Log.getStackTraceString(error)) }, {}) + } + }.invokeOnCompletion { throwable -> + if (throwable!= null) { + Logd(TAG, Log.getStackTraceString(throwable)) + } + } } override fun onDestroy() { super.onDestroy() - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } private fun setupFeedAutoSkipPreference() { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt index c4eba568..1176e1f2 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt @@ -6,15 +6,15 @@ import ac.mdiq.podcini.databinding.NavListBinding import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.NavDrawerData -import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity -import ac.mdiq.podcini.ui.adapter.NavListAdapter import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter -import ac.mdiq.podcini.ui.utils.ThemeUtils -import ac.mdiq.podcini.ui.dialog.* -import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils +import ac.mdiq.podcini.ui.adapter.NavListAdapter +import ac.mdiq.podcini.ui.dialog.DrawerPreferencesDialog +import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog import ac.mdiq.podcini.ui.statistics.StatisticsFragment +import ac.mdiq.podcini.ui.utils.ThemeUtils +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.FeedListUpdateEvent import ac.mdiq.podcini.util.event.QueueEvent import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent @@ -28,12 +28,13 @@ import android.graphics.Color import android.os.Build import android.os.Bundle import android.util.Log -import android.view.* -import android.widget.ProgressBar +import android.view.ContextMenu +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup import androidx.annotation.OptIn import androidx.annotation.VisibleForTesting import androidx.core.graphics.Insets -import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment @@ -42,10 +43,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.shape.MaterialShapeDrawable import com.google.android.material.shape.ShapeAppearanceModel -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.apache.commons.lang3.StringUtils import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe @@ -58,19 +56,17 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange private var navDrawerData: NavDrawerData? = null private var flatItemList: List? = null - private var contextPressedItem: NavDrawerData.FeedDrawerItem? = null - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) private lateinit var navAdapter: NavListAdapter - private lateinit var progressBar: ProgressBar - + private var openFolders: MutableSet = HashSet() override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) _binding = NavListBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") setupDrawerRoundBackground(binding.root) ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, insets: WindowInsetsCompat -> val bars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars()) @@ -88,9 +84,8 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange val preferences: SharedPreferences = requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) // TODO: what is this? - openFolders = HashSet(preferences.getStringSet(PREF_OPEN_FOLDERS, HashSet())) // Must not modify + openFolders = HashSet(preferences.getStringSet(PREF_OPEN_FOLDERS, HashSet())!!) // Must not modify - progressBar = binding.progressBar val navList = binding.navRecycler navAdapter = NavListAdapter(itemAccess, requireActivity()) navAdapter.setHasStableIds(true) @@ -129,64 +124,8 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange super.onDestroyView() _binding = null EventBus.getDefault().unregister(this) - disposable?.dispose() - - requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) - .unregisterOnSharedPreferenceChangeListener(this) - } - - override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { - super.onCreateContextMenu(menu, v, menuInfo) - val inflater: MenuInflater = requireActivity().menuInflater - if (contextPressedItem != null) { - menu.setHeaderTitle(contextPressedItem!!.title) - inflater.inflate(R.menu.nav_feed_context, menu) - // episodes are not loaded, so we cannot check if the podcast has new or unplayed ones! - } - MenuItemUtils.setOnClickListeners(menu - ) { item: MenuItem -> this.onContextItemSelected(item) } - } - - override fun onContextItemSelected(item: MenuItem): Boolean { - val pressedItem: NavDrawerData.FeedDrawerItem? = contextPressedItem - contextPressedItem = null - if (pressedItem == null) return false - - return onFeedContextMenuClicked(pressedItem.feed, item) - } - - @OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean { - val itemId = item.itemId - when (itemId) { - R.id.edit_tags -> { - if (feed.preferences != null) TagSettingsDialog.newInstance(listOf(feed.preferences!!)).show(childFragmentManager, TagSettingsDialog.TAG) - return true - } - R.id.rename_item -> { - RenameItemDialog(activity as Activity, feed).show() - return true - } - R.id.remove_feed -> { - RemoveFeedDialog.show(requireContext(), feed) { - if (feed.id.toString() == getLastNavFragment(requireContext())) { - (activity as MainActivity).loadFragment(UserPreferences.defaultPage, null) - // Make sure fragment is hidden before actually starting to delete - requireActivity().supportFragmentManager.executePendingTransactions() - } - } - return true - } - else -> return super.onContextItemSelected(item) - } - } - - private fun onTagContextMenuClicked(drawerItem: NavDrawerData.FeedDrawerItem?, item: MenuItem): Boolean { - val itemId = item.itemId - if (itemId == R.id.rename_folder_item) { - RenameItemDialog(activity as Activity, drawerItem).show() - return true - } - return super.onContextItemSelected(item) + scope.cancel() + requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this) } @Subscribe(threadMode = ThreadMode.MAIN) @@ -194,7 +133,6 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange loadData() } - @Subscribe(threadMode = ThreadMode.MAIN) fun onFeedListChanged(event: FeedListUpdateEvent?) { loadData() @@ -202,7 +140,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange @Subscribe(threadMode = ThreadMode.MAIN) fun onQueueChanged(event: QueueEvent) { - Log.d(TAG, "onQueueChanged($event)") + Logd(TAG, "onQueueChanged($event)") // we are only interested in the number of queue items, not download status or position if (event.action == QueueEvent.Action.DELETED_MEDIA || event.action == QueueEvent.Action.SORTED || event.action == QueueEvent.Action.MOVED) return @@ -245,12 +183,18 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange override val numberOfNewItems: Int get() = navDrawerData?.numNewItems ?: 0 + override val numberOfItems: Int + get() = navDrawerData?.numItems ?: 0 + override val numberOfDownloadedItems: Int get() = navDrawerData?.numDownloadedItems ?: 0 override val reclaimableItems: Int get() = navDrawerData?.reclaimableSpace ?: 0 + override val numberOfFeeds: Int + get() = navDrawerData?.numFeeds ?: 0 + override val feedCounterSum: Int get() { if (navDrawerData == null) return 0 @@ -297,7 +241,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange } return true } else { - contextPressedItem = flatItemList!![position - navAdapter.subscriptionOffset] +// contextPressedItem = flatItemList!![position - navAdapter.subscriptionOffset] return false } } @@ -308,22 +252,21 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange } private fun loadData() { - disposable = Observable.fromCallable { - val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) - Pair(data, makeFlatDrawerData(data.items, 0)) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { result: Pair> -> + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) + Pair(data, makeFlatDrawerData(data.items, 0)) + } + withContext(Dispatchers.Main) { navDrawerData = result.first flatItemList = result.second navAdapter.notifyDataSetChanged() - progressBar.visibility = View.GONE // Stays hidden once there is something in the list - }, { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - progressBar.visibility = View.GONE - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } } private fun makeFlatDrawerData(items: List, layer: Int): List { @@ -362,7 +305,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange ) fun saveLastNavFragment(context: Context, tag: String?) { - Log.d(TAG, "saveLastNavFragment(tag: $tag)") + Logd(TAG, "saveLastNavFragment(tag: $tag)") val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val edit: SharedPreferences.Editor = prefs.edit() if (tag != null) edit.putString(PREF_LAST_FRAGMENT_TAG, tag) @@ -374,7 +317,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange fun getLastNavFragment(context: Context): String { val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val lastFragment: String = prefs.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionFragment.TAG)?:"" - Log.d(TAG, "getLastNavFragment() -> $lastFragment") + Logd(TAG, "getLastNavFragment() -> $lastFragment") return lastFragment } } 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 7e310913..64449b51 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 @@ -54,12 +54,12 @@ import androidx.media3.common.util.UnstableApi import coil.load import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import io.reactivex.Maybe -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable -import io.reactivex.observers.DisposableMaybeObserver import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -98,6 +98,8 @@ class OnlineFeedViewFragment : Fragment() { private var dialog: Dialog? = null + val scope = CoroutineScope(Dispatchers.Main) + private var download: Disposable? = null private var parser: Disposable? = null private var updater: Disposable? = null @@ -197,6 +199,18 @@ class OnlineFeedViewFragment : Fragment() { Log.e(TAG, Log.getStackTraceString(error)) } }) +// scope.launch(Dispatchers.IO) { +// try { +// startFeedDownload(url) +// } catch (e: FeedUrlNotFoundException) { +// tryToRetrieveFeedUrlBySearch(e) +// } catch (e: Throwable) { +// withContext(Dispatchers.Main) { +// showNoPodcastFoundError() +// Log.e(TAG, Log.getStackTraceString(e)) +// } +// } +// } } private fun tryToRetrieveFeedUrlBySearch(error: FeedUrlNotFoundException) { @@ -269,16 +283,32 @@ class OnlineFeedViewFragment : Fragment() { .withInitiatedByUser(true) .build() - download = Observable.fromCallable { - feeds = DBReader.getFeedList() - downloader = HttpDownloader(request) - downloader?.call() - downloader?.result +// 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)) }) + + scope.launch { + try { + val status = withContext(Dispatchers.IO) { + feeds = DBReader.getFeedList() + downloader = HttpDownloader(request) + downloader?.call() + downloader?.result + } + withContext(Dispatchers.Main) { + if (request.destination != null) checkDownloadResult(status, request.destination) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } } - .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)) }) } private fun checkDownloadResult(status: DownloadResult?, destination: String) { @@ -301,15 +331,30 @@ class OnlineFeedViewFragment : Fragment() { @UnstableApi @Subscribe fun onFeedListChanged(event: FeedListUpdateEvent?) { - updater = Observable.fromCallable { DBReader.getFeedList() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { feeds: List? -> +// 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)) } +// ) + scope.launch { + try { + val feeds = withContext(Dispatchers.IO) { + DBReader.getFeedList() + } + withContext(Dispatchers.Main) { this@OnlineFeedViewFragment.feeds = feeds handleUpdatedFeedStatus() - }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) } - ) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } + + } @UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) @@ -317,25 +362,40 @@ class OnlineFeedViewFragment : Fragment() { handleUpdatedFeedStatus() } - private fun parseFeed(destination: String) { + @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) +// 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)) +// } +// }) + scope.launch { + try { + val result = withContext(Dispatchers.Default) { + doParseFeed(destination) } - - override fun onComplete() { - // Ignore null result: We showed the discovery dialog. + withContext(Dispatchers.Main) { + if (result != null) showFeedInformation(result.feed, result.alternateFeedUrls) } - - override fun onError(error: Throwable) { - showErrorDialog(error.message, "") - Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(error)) + } catch (e: Throwable) { + withContext(Dispatchers.Main) { + showErrorDialog(e.message, "") + Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(e)) } - }) + } + } } /** diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt index c003594f..dc8e92a2 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.net.discovery.PodcastSearcher import ac.mdiq.podcini.net.discovery.PodcastSearcherRegistry import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.OnlineFeedsAdapter +import ac.mdiq.podcini.util.Logd import android.content.Context import android.os.Bundle import android.util.Log @@ -46,7 +47,7 @@ class OnlineSearchFragment : Fragment() { super.onCreate(savedInstanceState) for (info in PodcastSearcherRegistry.searchProviders) { - Log.d(TAG, "searchProvider: $info") + Logd(TAG, "searchProvider: $info") if (info.searcher.javaClass.getName() == requireArguments().getString(ARG_SEARCHER)) { searchProvider = info.searcher break @@ -58,7 +59,7 @@ class OnlineSearchFragment : Fragment() { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentItunesSearchBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") gridView = binding.gridView adapter = OnlineFeedsAdapter(requireContext(), ArrayList()) gridView.setAdapter(adapter) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlaybackHistoryFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlaybackHistoryFragment.kt index 2ea8c407..5eaeb2e7 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlaybackHistoryFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlaybackHistoryFragment.kt @@ -14,6 +14,7 @@ import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.util.event.playback.PlaybackHistoryEvent import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItemFilter +import ac.mdiq.podcini.util.Logd import android.util.Log import androidx.annotation.OptIn import org.greenrobot.eventbus.Subscribe @@ -30,7 +31,7 @@ class PlaybackHistoryFragment : BaseEpisodesListFragment() { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val root = super.onCreateView(inflater, container, savedInstanceState) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar.inflateMenu(R.menu.playback_history) toolbar.setTitle(R.string.playback_history_label) updateToolbar() 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 8144d8d2..a7f69073 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 @@ -16,6 +16,7 @@ import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.view.ShownotesWebView import ac.mdiq.podcini.util.ChapterUtils import ac.mdiq.podcini.util.DateFormatter +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.NetworkUtils.fetchHtmlSource import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent import android.animation.Animator @@ -46,12 +47,7 @@ import coil.request.ErrorResult import coil.request.ImageRequest import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.snackbar.Snackbar -import io.reactivex.Maybe -import io.reactivex.MaybeEmitter -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.* import net.dankito.readability4j.Readability4J import org.apache.commons.lang3.StringUtils import org.greenrobot.eventbus.Subscribe @@ -61,20 +57,20 @@ import org.greenrobot.eventbus.ThreadMode * Displays the description of a Playable object in a Webview. */ @UnstableApi -class PlayerDetailsFragment : Fragment() { +class PlayerDetailsFragment : Fragment() { private lateinit var shownoteView: ShownotesWebView private var _binding: PlayerDetailsFragmentBinding? = null private val binding get() = _binding!! + private var prevItem: FeedItem? = null private var media: Playable? = null private var item: FeedItem? = null - private var loadedMediaId: Any? = null private var displayedChapterIndex = -1 + val scope = CoroutineScope(Dispatchers.Main) private var cleanedNotes: String? = null - private var disposable: Disposable? = null - private var webViewLoader: Disposable? = null +// private var webViewLoader: Disposable? = null private var controller: PlaybackController? = null internal var showHomeText = false @@ -82,7 +78,7 @@ class PlayerDetailsFragment : Fragment() { internal var readerhtml: String? = null @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") _binding = PlayerDetailsFragmentBinding.inflate(inflater) binding.imgvCover.setOnClickListener { onPlayPause() } @@ -94,7 +90,7 @@ class PlayerDetailsFragment : Fragment() { binding.butPrevChapter.setOnClickListener { seekToPrevChapter() } binding.butNextChapter.setOnClickListener { seekToNextChapter() } - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") shownoteView = binding.webview shownoteView.setTimecodeSelectedListener { time: Int? -> controller?.seekTo(time!!) } shownoteView.setPageFinishedListener { @@ -123,7 +119,7 @@ class PlayerDetailsFragment : Fragment() { _binding = null controller?.release() controller = null - Log.d(TAG, "Fragment destroyed") + Logd(TAG, "Fragment destroyed") shownoteView.removeAllViews() shownoteView.destroy() } @@ -132,60 +128,85 @@ class PlayerDetailsFragment : Fragment() { return shownoteView.onContextItemSelected(item) } - @UnstableApi private fun load() { - Log.d(TAG, "load() called") - webViewLoader?.dispose() +// @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 - webViewLoader = Maybe.create { emitter: MaybeEmitter -> - media = controller?.getMedia() - if (media == null) { - emitter.onComplete() - return@create - } - if (media is FeedMedia) { - val feedMedia = media as FeedMedia - if (item?.itemIdentifier != feedMedia.item?.itemIdentifier) { - item = feedMedia.item - showHomeText = false - homeText = null + scope.launch { + withContext(Dispatchers.IO) { + if (item == null) { + media = controller?.getMedia() + if (media != null && 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 } } -// Log.d(TAG, "webViewLoader ${item?.id} ${cleanedNotes==null} ${item!!.description==null} ${loadedMediaId == null} ${item?.media?.getIdentifier()} ${media?.getIdentifier()}") - if (item != null) { - if (cleanedNotes == null || item!!.description == null || loadedMediaId != media?.getIdentifier()) { - Log.d(TAG, "calling load description ${cleanedNotes==null} ${item!!.description==null} ${item!!.media?.getIdentifier()} ${media?.getIdentifier()}") -// printStackTrace() - DBReader.loadTextDetailsOfFeedItem(item!!) - loadedMediaId = media?.getIdentifier() - val shownotesCleaner = ShownotesCleaner(context, item?.description ?: "", media?.getDuration()?:0) - cleanedNotes = shownotesCleaner.processShownotes() - } + withContext(Dispatchers.Main) { + Logd(TAG, "subscribe: ${media?.getEpisodeTitle()}") + displayMediaInfo(media!!) + shownoteView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes!!, "text/html", "utf-8", "about:blank") + Logd(TAG, "Webview loaded") + } + }.invokeOnCompletion { throwable -> + if (throwable!= null) { + Log.e(TAG, Log.getStackTraceString(throwable)) } - emitter.onSuccess(cleanedNotes?:"") } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ data: String? -> - shownoteView.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html", "utf-8", "about:blank") - Log.d(TAG, "Webview loaded") - }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) - - loadMediaInfo() - } - - @UnstableApi private fun loadMediaInfo() { - disposable?.dispose() - disposable = Maybe.create { emitter: MaybeEmitter -> - media = controller?.getMedia() - if (media != null) emitter.onSuccess(media!!) - else emitter.onComplete() - }.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ media: Playable -> - this.media = media - displayMediaInfo(media) - }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) } fun buildHomeReaderText() { @@ -220,27 +241,21 @@ class PlayerDetailsFragment : Fragment() { } @UnstableApi private fun displayMediaInfo(media: Playable) { + Logd(TAG, "displayMediaInfo ${item?.title} ${media.getEpisodeTitle()}") val pubDateStr = DateFormatter.formatAbbrev(context, media.getPubDate()) binding.txtvPodcastTitle.text = StringUtils.stripToEmpty(media.getFeedTitle()) - if (item == null || item!!.media?.getIdentifier() != media.getIdentifier()) { - if (media is FeedMedia) { - if (item?.itemIdentifier != media.item?.itemIdentifier) { - item = media.item - showHomeText = false - homeText = null - } - if (item != null) { - val openFeed: Intent = MainActivity.getIntentToOpenFeed(requireContext(), item!!.feedId) - binding.txtvPodcastTitle.setOnClickListener { startActivity(openFeed) } - } - } else { - binding.txtvPodcastTitle.setOnClickListener(null) + if (media is FeedMedia) { + if (item != null) { + val openFeed: Intent = MainActivity.getIntentToOpenFeed(requireContext(), item!!.feedId) + binding.txtvPodcastTitle.setOnClickListener { startActivity(openFeed) } } + } else { + binding.txtvPodcastTitle.setOnClickListener(null) } binding.txtvPodcastTitle.setOnLongClickListener { copyText(media.getFeedTitle()) } binding.episodeDate.text = StringUtils.stripToEmpty(pubDateStr) - binding.txtvEpisodeTitle.text = media.getEpisodeTitle() - binding.txtvEpisodeTitle.setOnLongClickListener { copyText(media.getEpisodeTitle()) } + binding.txtvEpisodeTitle.text = item?.title + binding.txtvEpisodeTitle.setOnLongClickListener { copyText(item?.title?:"") } binding.txtvEpisodeTitle.setOnClickListener { val lines = binding.txtvEpisodeTitle.lineCount val animUnit = 1500 @@ -300,19 +315,7 @@ class PlayerDetailsFragment : Fragment() { private fun displayCoverImage() { if (media == null) return -// val options: RequestOptions = RequestOptions() -// .dontAnimate() -// .transform(FitCenter(), RoundedCorners((16 * resources.displayMetrics.density).toInt())) - -// val cover: RequestBuilder = Glide.with(this) -// .load(media!!.getImageLocation()) -// .error(Glide.with(this) -// .load(ImageResourceUtils.getFallbackImageLocation(media!!)) -// .apply(options)) -// .apply(options) - if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) { -// cover.into(binding.imgvCover) val imageLoader = binding.imgvCover.context.imageLoader val imageRequest = ImageRequest.Builder(requireContext()) .data(media!!.getImageLocation()) @@ -335,12 +338,6 @@ class PlayerDetailsFragment : Fragment() { } else { val imgLoc = EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex) -// if (imgLoc != null) Glide.with(this) -// .load(imgLoc) -// .apply(options) -// .thumbnail(cover) -// .error(cover) -// .into(binding.imgvCover) val imageLoader = binding.imgvCover.context.imageLoader val imageRequest = ImageRequest.Builder(requireContext()) .data(imgLoc) @@ -402,15 +399,15 @@ class PlayerDetailsFragment : Fragment() { } @UnstableApi private fun savePreference() { - Log.d(TAG, "Saving preferences") + Logd(TAG, "Saving preferences") val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE) val editor = prefs.edit() if (controller?.getMedia() != null) { - Log.d(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY) + Logd(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY) editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY) editor.putString(PREF_PLAYABLE_ID, controller!!.getMedia()!!.getIdentifier().toString()) } else { - Log.d(TAG, "savePreferences was called while media or webview was null") + Logd(TAG, "savePreferences was called while media or webview was null") editor.putInt(PREF_SCROLL_Y, -1) editor.putString(PREF_PLAYABLE_ID, "") } @@ -420,7 +417,7 @@ class PlayerDetailsFragment : Fragment() { @UnstableApi private fun restoreFromPreference(): Boolean { if ((activity as MainActivity).bottomSheet.state != BottomSheetBehavior.STATE_EXPANDED) return false - Log.d(TAG, "Restoring from preferences") + Logd(TAG, "Restoring from preferences") val activity: Activity? = activity if (activity != null) { val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE) @@ -428,11 +425,11 @@ class PlayerDetailsFragment : Fragment() { val scrollY = prefs.getInt(PREF_SCROLL_Y, -1) if (scrollY != -1) { if (id == controller?.getMedia()?.getIdentifier()?.toString()) { - Log.d(TAG, "Restored scroll Position: $scrollY") + Logd(TAG, "Restored scroll Position: $scrollY") binding.itemDescriptionFragment.scrollTo(binding.itemDescriptionFragment.scrollX, scrollY) return true } - Log.d(TAG, "reset scroll Position: 0") + Logd(TAG, "reset scroll Position: 0") binding.itemDescriptionFragment.scrollTo(0, 0) return true @@ -455,7 +452,7 @@ class PlayerDetailsFragment : Fragment() { } fun setItem(item_: FeedItem) { - Log.d(TAG, "setItem ${item_.title}") + Logd(TAG, "setItem ${item_.title}") if (item?.itemIdentifier != item_.itemIdentifier) { item = item_ showHomeText = false @@ -491,7 +488,7 @@ class PlayerDetailsFragment : Fragment() { override fun onStop() { super.onStop() - webViewLoader?.dispose() +// webViewLoader?.dispose() } @UnstableApi private fun copyText(text: String): Boolean { @@ -504,7 +501,7 @@ class PlayerDetailsFragment : Fragment() { } companion object { - private const val TAG = "ItemDescriptionFragment" + private const val TAG = "PlayerDetailsFragment" private const val PREF = "ItemDescriptionFragmentPrefs" private const val PREF_SCROLL_Y = "prefScrollY" 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 3ebb0460..49790184 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 @@ -6,27 +6,30 @@ import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding import ac.mdiq.podcini.databinding.QueueFragmentBinding import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils import ac.mdiq.podcini.net.download.FeedUpdateManager -import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBWriter -import ac.mdiq.podcini.storage.model.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.ui.actions.EpisodeMultiSelectActionHandler +import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler +import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils +import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ItemSortDialog -import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectActionHandler -import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions -import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler -import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.util.Converter import ac.mdiq.podcini.util.FeedItemUtil +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.* +import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent import android.content.Context import android.content.DialogInterface import android.content.SharedPreferences @@ -48,10 +51,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -81,7 +81,8 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda private var recyclerAdapter: QueueRecyclerAdapter? = null private var currentPlaying: EpisodeItemViewHolder? = null - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -93,7 +94,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda super.onCreateView(inflater, container, savedInstanceState) _binding = QueueFragmentBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar = binding.toolbar toolbar.setOnMenuItemClickListener(this) toolbar.setOnLongClickListener { @@ -194,12 +195,13 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda override fun onStop() { super.onStop() - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } @Subscribe(threadMode = ThreadMode.MAIN) fun onEventMainThread(event: QueueEvent) { - Log.d(TAG, "onEventMainThread() called with QueueEvent event = [$event]") + Logd(TAG, "onEventMainThread() called with QueueEvent event = [$event]") if (recyclerAdapter == null) { loadItems(true) return @@ -236,7 +238,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @Subscribe(threadMode = ThreadMode.MAIN) fun onEventMainThread(event: FeedItemEvent) { - Log.d(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]") + Logd(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]") if (recyclerAdapter == null) { loadItems(true) return @@ -258,7 +260,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) fun onEventMainThread(event: EpisodeDownloadEvent) { - Log.d(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]") + Logd(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]") for (downloadUrl in event.urls) { val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue.toList(), downloadUrl) if (pos >= 0) recyclerAdapter?.notifyItemChangedCompat(pos) @@ -271,7 +273,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda if (recyclerAdapter != null) { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem) currentPlaying!!.notifyPlaybackPositionUpdated(event) else { - Log.d(TAG, "onEventMainThread() search list") + Logd(TAG, "onEventMainThread() search list") for (i in 0 until recyclerAdapter!!.itemCount) { val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder if (holder != null && holder.isCurrentlyPlayingItem) { @@ -286,7 +288,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @Subscribe(threadMode = ThreadMode.MAIN) fun onPlayerStatusChanged(event: PlayerStatusEvent?) { - Log.d(TAG, "onPlayerStatusChanged() called with event = [$event]") + Logd(TAG, "onPlayerStatusChanged() called with event = [$event]") loadItems(false) refreshToolbarState() } @@ -294,7 +296,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @Subscribe(threadMode = ThreadMode.MAIN) fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) { // Sent when playback position is reset - Log.d(TAG, "onUnreadItemsChanged() called with event = [$event]") + Logd(TAG, "onUnreadItemsChanged() called with event = [$event]") loadItems(false) refreshToolbarState() } @@ -426,7 +428,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda } @UnstableApi override fun onContextItemSelected(item: MenuItem): Boolean { - Log.d(TAG, "onContextItemSelected() called with: item = [$item]") + Logd(TAG, "onContextItemSelected() called with: item = [$item]") if (!isVisible || recyclerAdapter == null) return false val selectedItem: FeedItem? = recyclerAdapter!!.longPressedItem @@ -485,22 +487,38 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda } private fun loadItems(restoreScrollPosition: Boolean) { - Log.d(TAG, "loadItems() called") - disposable?.dispose() + 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)) }) +// 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)) }) + + scope.launch { + try { + queue = withContext(Dispatchers.IO) { DBReader.getQueue().toMutableList() } + withContext(Dispatchers.Main) { + progressBar.visibility = View.GONE + recyclerAdapter?.setDummyViews(0) + recyclerAdapter?.updateItems(queue) + if (restoreScrollPosition) recyclerView.restoreScrollPosition(TAG) + refreshInfoBar() + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } + } override fun onStartSelectMode() { @@ -562,7 +580,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda val from = viewHolder.bindingAdapterPosition val to = target.bindingAdapterPosition - Log.d(TAG, "move($from, $to) in memory") + Logd(TAG, "move($from, $to) in memory") if (from >= queue.size || to >= queue.size || from < 0 || to < 0) return false queue.add(to, queue.removeAt(from)) @@ -571,7 +589,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda } @UnstableApi override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - disposable?.dispose() +// disposable?.dispose() //SwipeActions super.onSwiped(viewHolder, direction) @@ -592,7 +610,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @UnstableApi private fun reallyMoved(from: Int, to: Int) { // Write drag operation to database - Log.d(TAG, "Write to database move($from, $to)") + Logd(TAG, "Write to database move($from, $to)") DBWriter.moveQueueItem(from, to, true) } } 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 1fabf8ae..ad6a9931 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 @@ -8,6 +8,7 @@ import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.FeedDiscoverAdapter +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.DiscoveryDefaultUpdateEvent import android.content.Context import android.content.SharedPreferences @@ -21,10 +22,7 @@ import android.widget.* import androidx.annotation.OptIn import androidx.fragment.app.Fragment import androidx.media3.common.util.UnstableApi -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -34,8 +32,9 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { private var _binding: QuickFeedDiscoveryBinding? = null private val binding get() = _binding!! - private var disposable: Disposable? = null - +// private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) + private lateinit var adapter: FeedDiscoverAdapter private lateinit var discoverGridLayout: GridView private lateinit var errorTextView: TextView @@ -47,7 +46,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { super.onCreateView(inflater, container, savedInstanceState) _binding = QuickFeedDiscoveryBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") val discoverMore = binding.discoverMore discoverMore.setOnClickListener { (activity as MainActivity).loadChildFragment(DiscoveryFragment()) } @@ -84,7 +83,8 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { super.onDestroy() _binding = null EventBus.getDefault().unregister(this) - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } @Subscribe(threadMode = ThreadMode.MAIN) @@ -124,13 +124,37 @@ 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 -> +// 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() } +// }) + + scope.launch { + try { + val podcasts = withContext(Dispatchers.IO) { + loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList()) + } + withContext(Dispatchers.Main) { errorView.visibility = View.GONE if (podcasts.isEmpty()) { errorTextView.text = resources.getText(R.string.search_status_no_results) @@ -140,14 +164,17 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { 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() } - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + errorTextView.text = e.localizedMessage + errorView.visibility = View.VISIBLE + discoverGridLayout.visibility = View.INVISIBLE + errorRetry.visibility = View.VISIBLE + errorRetry.setOnClickListener { loadToplist() } + } + } + } @OptIn(UnstableApi::class) override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) { 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 eb0f4ca5..b3f11149 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 @@ -5,24 +5,25 @@ import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding import ac.mdiq.podcini.databinding.SearchFragmentBinding import ac.mdiq.podcini.net.discovery.CombinedSearcher -import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent import ac.mdiq.podcini.storage.FeedSearcher import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.FeedItem -import ac.mdiq.podcini.ui.activity.MainActivity -import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter -import ac.mdiq.podcini.ui.adapter.HorizontalFeedListAdapter -import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectActionHandler import ac.mdiq.podcini.ui.actions.menuhandler.FeedItemMenuHandler import ac.mdiq.podcini.ui.actions.menuhandler.FeedMenuHandler import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils +import ac.mdiq.podcini.ui.activity.MainActivity +import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter +import ac.mdiq.podcini.ui.adapter.HorizontalFeedListAdapter +import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.util.FeedItemUtil +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.* +import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent import android.content.Context import android.os.Bundle import android.os.Handler @@ -42,10 +43,7 @@ import com.google.android.material.chip.Chip import com.google.android.material.snackbar.Snackbar import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -70,7 +68,8 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { private var results: MutableList = mutableListOf() private var currentPlaying: EpisodeItemViewHolder? = null - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null private var lastQueryChange: Long = 0 private var isOtherViewInFoucus = false @@ -82,13 +81,14 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { override fun onStop() { super.onStop() - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = SearchFragmentBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") setupToolbar(binding.toolbar) speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root) progressBar = binding.progressBar @@ -243,7 +243,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) fun onEventMainThread(event: FeedItemEvent) { - Log.d(TAG, "onEventMainThread() called with: event = [$event]") + Logd(TAG, "onEventMainThread() called with: event = [$event]") var i = 0 val size: Int = event.items.size @@ -272,7 +272,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem) currentPlaying!!.notifyPlaybackPositionUpdated(event) else { - Log.d(TAG, "onEventMainThread() search list") + Logd(TAG, "onEventMainThread() search list") for (i in 0 until adapter.itemCount) { val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder if (holder != null && holder.isCurrentlyPlayingItem) { @@ -296,27 +296,50 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { } @UnstableApi private fun search() { - disposable?.dispose() +// 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!!) +// 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)) }) + + scope.launch { + try { + val results = withContext(Dispatchers.IO) { + performSearch() } - if (requireArguments().getLong(ARG_FEED, 0) == 0L) { - if (results.second != null) adapterFeeds.updateData(results.second!!.filterNotNull()) - } else adapterFeeds.updateData(emptyList()) + withContext(Dispatchers.Main) { + progressBar.visibility = View.GONE + if (results.first != null) { + this@SearchFragment.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)) }) + if (searchView.query.toString().isEmpty()) emptyViewHandler.setMessage(R.string.type_to_search) + else emptyViewHandler.setMessage(getString(R.string.no_results_for_query) + searchView.query) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } } @UnstableApi private fun performSearch(): Pair?, List?> { 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 189b0b58..87022476 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 @@ -8,16 +8,17 @@ import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.NavDrawerData import ac.mdiq.podcini.storage.model.feed.Feed +import ac.mdiq.podcini.ui.actions.FeedMultiSelectActionHandler +import ac.mdiq.podcini.ui.actions.menuhandler.FeedMenuHandler +import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SubscriptionsAdapter import ac.mdiq.podcini.ui.dialog.FeedSortDialog import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog -import ac.mdiq.podcini.ui.actions.FeedMultiSelectActionHandler -import ac.mdiq.podcini.ui.actions.menuhandler.FeedMenuHandler -import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.view.LiftOnScrollListener +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.FeedListUpdateEvent import ac.mdiq.podcini.util.event.FeedTagsChangedEvent import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent @@ -39,10 +40,7 @@ import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.floatingactionbutton.FloatingActionButton import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialView -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode @@ -72,7 +70,8 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select private var displayedFolder: String = "" private var displayUpArrow = false - private var disposable: Disposable? = null + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null private var feedList: List = mutableListOf() private var feedListFiltered: List = mutableListOf() @@ -86,7 +85,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { _binding = FragmentSubscriptionsBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") + Logd(TAG, "fragment onCreateView") toolbar = binding.toolbar toolbar.setOnMenuItemClickListener(this) toolbar.setOnLongClickListener { @@ -201,7 +200,8 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select super.onDestroyView() _binding = null EventBus.getDefault().unregister(this) - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } override fun onSaveInstanceState(outState: Bundle) { @@ -272,17 +272,37 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select } private fun loadSubscriptions() { - disposable?.dispose() +// 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 -> +// 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)) +// }) + + scope.launch { + try { + val result = withContext(Dispatchers.IO) { + val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter) + val items: List = data.items + items + } + withContext(Dispatchers.Main) { // 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 @@ -291,9 +311,11 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select subscriptionAdapter.setItems(feedListFiltered) feedCount.text = feedListFiltered.size.toString() + " / " + feedList.size.toString() emptyView.updateVisibility() - }, { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - }) + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } if (UserPreferences.subscriptionsFilter.isEnabled) feedsFilteredMsg.visibility = View.VISIBLE else feedsFilteredMsg.visibility = View.GONE 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 e4034235..6cff0fbc 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 @@ -23,6 +23,7 @@ import ac.mdiq.podcini.ui.utils.PictureInPictureUtil import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.view.ShownotesWebView import ac.mdiq.podcini.util.Converter.getDurationStringLong +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.TimeSpeedConverter import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent import ac.mdiq.podcini.util.event.playback.PlaybackPositionEvent @@ -41,13 +42,11 @@ import androidx.core.app.ActivityCompat.invalidateOptionsMenu import androidx.fragment.app.Fragment import androidx.interpolator.view.animation.FastOutSlowInInterpolator import androidx.media3.common.util.UnstableApi -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers +import kotlinx.coroutines.* import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode +import java.lang.Runnable @UnstableApi class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { @@ -63,7 +62,9 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { private var lastScreenTap: Long = 0 private val videoControlsHider = Handler(Looper.getMainLooper()) private var showTimeLeft = false - private var disposable: Disposable? = null + + val scope = CoroutineScope(Dispatchers.Main) +// private var disposable: Disposable? = null private var prog = 0f private var itemsLoaded = false @@ -92,7 +93,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { @OptIn(UnstableApi::class) private fun newPlaybackController(): PlaybackController { return object : PlaybackController(requireActivity()) { override fun updatePlayButtonShowsPlay(showPlay: Boolean) { - Log.d(TAG, "updatePlayButtonShowsPlay called") + Logd(TAG, "updatePlayButtonShowsPlay called") binding.playButton.setIsShowPlay(showPlay) if (showPlay) { (activity as AppCompatActivity).window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) @@ -100,7 +101,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { (activity as AppCompatActivity).window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) setupVideoAspectRatio() if (videoSurfaceCreated && controller != null) { - Log.d(TAG, "Videosurface already created, setting videosurface now") + Logd(TAG, "Videosurface already created, setting videosurface now") setVideoSurface(binding.videoView.holder) } } @@ -148,7 +149,8 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { _binding = null controller?.release() controller = null // prevent leak - disposable?.dispose() + scope.cancel() +// disposable?.dispose() } @Subscribe(threadMode = ThreadMode.MAIN) @@ -164,10 +166,10 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { private fun setupVideoAspectRatio() { if (videoSurfaceCreated && controller != null) { if (videoSize != null && videoSize!!.first > 0 && videoSize!!.second > 0) { - Log.d(TAG, "Width,height of video: " + videoSize!!.first + ", " + videoSize!!.second) + Logd(TAG, "Width,height of video: ${videoSize!!.first}, ${videoSize!!.second}") val videoWidth = resources.displayMetrics.widthPixels val videoHeight = (videoWidth.toFloat() / videoSize!!.first * videoSize!!.second).toInt() - Log.d(TAG, "Width,height of video: " + videoWidth + ", " + videoHeight) + Logd(TAG, "Width,height of video: $videoWidth, $videoHeight") binding.videoView.setVideoSize(videoWidth, videoHeight) // binding.videoView.setVideoSize(videoSize.first, videoSize.second) // binding.videoView.setVideoSize(-1, -1) @@ -175,18 +177,18 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { Log.e(TAG, "Could not determine video size") val videoWidth = resources.displayMetrics.widthPixels val videoHeight = (videoWidth.toFloat() / 16 * 9).toInt() - Log.d(TAG, "Width,height of video: " + videoWidth + ", " + videoHeight) + Logd(TAG, "Width,height of video: $videoWidth, $videoHeight") binding.videoView.setVideoSize(videoWidth, videoHeight) } } } @OptIn(UnstableApi::class) private fun loadMediaInfo() { - Log.d(TAG, "loadMediaInfo called") + Logd(TAG, "loadMediaInfo called") if (controller?.getMedia() == null) return if (MediaPlayerBase.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) { - Log.d(TAG, "Closing, no longer video") + Logd(TAG, "Closing, no longer video") destroyingDueToReload = true activity?.finish() MainActivityStarter(requireContext()).withOpenPlayer().start() @@ -203,27 +205,50 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { } @UnstableApi private fun load() { - disposable?.dispose() - Log.d(TAG, "load() called") +// disposable?.dispose() + Logd(TAG, "load() called") - disposable = Observable.fromCallable { this.loadInBackground() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result: FeedItem? -> - item = result - Log.d(TAG, "load() item ${item?.id}") - if (item != null) { - val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE) - if (isFavorite != isFav) { - isFavorite = isFav - invalidateOptionsMenu(requireActivity()) - } +// 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)) +// }) + + scope.launch { + try { + item = withContext(Dispatchers.IO) { + loadInBackground() } - onFragmentLoaded() - itemsLoaded = true - }, { error: Throwable? -> - Log.e(TAG, Log.getStackTraceString(error)) - }) + withContext(Dispatchers.Main) { + 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 + } + } catch (e: Throwable) { + Log.e(TAG, Log.getStackTraceString(e)) + } + } + } private fun loadInBackground(): FeedItem? { @@ -244,7 +269,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { @UnstableApi private fun setupView() { showTimeLeft = shouldShowRemainingTime() - Log.d(TAG, "setupView showTimeLeft: $showTimeLeft") + Logd(TAG, "setupView showTimeLeft: $showTimeLeft") binding.durationLabel.setOnClickListener { showTimeLeft = !showTimeLeft @@ -262,7 +287,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { binding.durationLabel.text = length setShowRemainTimeSetting(showTimeLeft) - Log.d("timeleft on click", if (showTimeLeft) "true" else "false") + Logd("timeleft on click", if (showTimeLeft) "true" else "false") } binding.sbPosition.setOnSeekBarChangeListener(this) @@ -394,14 +419,14 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { @UnstableApi override fun surfaceCreated(holder: SurfaceHolder) { - Log.d(TAG, "Videoview holder created") + Logd(TAG, "Videoview holder created") videoSurfaceCreated = true if (MediaPlayerBase.status == PlayerStatus.PLAYING) setVideoSurface(holder) setupVideoAspectRatio() } override fun surfaceDestroyed(holder: SurfaceHolder) { - Log.d(TAG, "Videosurface was destroyed") + Logd(TAG, "Videosurface was destroyed") videoSurfaceCreated = false if (controller != null && !destroyingDueToReload && !(activity as VideoplayerActivity).switchToAudioOnly) notifyVideoSurfaceAbandoned() @@ -441,7 +466,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { private val hideVideoControls = Runnable { if (videoControlsShowing) { - Log.d(TAG, "Hiding video controls") + Logd(TAG, "Hiding video controls") hideVideoControls(true) if (videoMode == VideoplayerActivity.VideoMode.FULL_SCREEN_VIEW) (activity as? AppCompatActivity)?.supportActionBar?.hide() videoControlsShowing = false @@ -504,7 +529,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener { } private fun updateProgressbarPosition(position: Int, duration: Int) { - Log.d(TAG, "updateProgressbarPosition($position, $duration)") + Logd(TAG, "updateProgressbarPosition ($position, $duration)") val progress = (position.toFloat()) / duration binding.sbPosition.progress = (progress * binding.sbPosition.max).toInt() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/view/viewholder/EpisodeItemViewHolder.kt b/app/src/main/java/ac/mdiq/podcini/ui/view/viewholder/EpisodeItemViewHolder.kt index 7315b1fb..136aa8b3 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/view/viewholder/EpisodeItemViewHolder.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/view/viewholder/EpisodeItemViewHolder.kt @@ -138,7 +138,7 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou if (coverHolder.visibility == View.VISIBLE) { val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(item) - Logd(TAG, "imgLoc $imgLoc ${item.feed?.imageUrl} ${item.title}") +// Logd(TAG, "imgLoc $imgLoc ${item.feed?.imageUrl} ${item.title}") if (!imgLoc.isNullOrBlank() && !imgLoc.contains(PREFIX_GENERATIVE_COVER)) CoverLoader(activity) .withUri(imgLoc) .withFallbackUri(item.feed?.imageUrl) diff --git a/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt b/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt index 8fa46fa8..ae7eff7b 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt @@ -61,7 +61,7 @@ object ChapterUtils { val chaptersFromMediaFile = loadChaptersFromMediaFile(playable, context) val chaptersMergePhase1 = merge(chaptersFromDatabase, chaptersFromMediaFile) val chapters = merge(chaptersMergePhase1, chaptersFromPodcastIndex) - Log.d(TAG, "loadChapters chapters size: ${chapters?.size?:0} ${playable.getEpisodeTitle()}") + Logd(TAG, "loadChapters chapters size: ${chapters?.size?:0} ${playable.getEpisodeTitle()}") if (chapters == null) playable.setChapters(listOf()) // Do not try loading again. There are no chapters. else playable.setChapters(chapters) diff --git a/app/src/main/java/ac/mdiq/podcini/util/FeedItemPermutors.kt b/app/src/main/java/ac/mdiq/podcini/util/FeedItemPermutors.kt index 09fceed4..8c951c70 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/FeedItemPermutors.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/FeedItemPermutors.kt @@ -97,7 +97,7 @@ object FeedItemPermutors { } private fun feedTitle(item: FeedItem?): String { - Log.d("permutors", "feedTitle ${item?.feed?.title}") + Logd("permutors", "feedTitle ${item?.feed?.title}") return if (item?.feed?.title != null) item.feed!!.title!!.lowercase(Locale.getDefault()) else "" } diff --git a/app/src/main/java/ac/mdiq/podcini/util/URIUtil.kt b/app/src/main/java/ac/mdiq/podcini/util/URIUtil.kt index 48044b97..f2e7e04f 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/URIUtil.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/URIUtil.kt @@ -24,10 +24,10 @@ object URIUtil { val url = URL(source) return URI(url.protocol, url.userInfo, url.host, url.port, url.path, url.query, url.ref) } catch (e: MalformedURLException) { - Log.d(TAG, "source: $source") + Logd(TAG, "source: $source") throw IllegalArgumentException(e) } catch (e: URISyntaxException) { - Log.d(TAG, "source: $source") + Logd(TAG, "source: $source") throw IllegalArgumentException(e) } } diff --git a/app/src/main/java/ac/mdiq/podcini/util/error/RxJavaErrorHandlerSetup.kt b/app/src/main/java/ac/mdiq/podcini/util/error/RxJavaErrorHandlerSetup.kt index 58fabefa..165dca98 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/error/RxJavaErrorHandlerSetup.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/error/RxJavaErrorHandlerSetup.kt @@ -1,6 +1,7 @@ package ac.mdiq.podcini.util.error import ac.mdiq.podcini.BuildConfig +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.error.CrashReportWriter.Companion.write import android.util.Log import io.reactivex.exceptions.UndeliverableException @@ -13,7 +14,7 @@ object RxJavaErrorHandlerSetup { RxJavaPlugins.setErrorHandler { exception: Throwable? -> if (exception is UndeliverableException) { // Probably just disposed because the fragment was left - Log.d(TAG, "Ignored exception: " + Log.getStackTraceString(exception)) + Logd(TAG, "Ignored exception: " + Log.getStackTraceString(exception)) return@setErrorHandler } // Usually, undeliverable exceptions are wrapped in an UndeliverableException. diff --git a/app/src/main/res/layout/nav_list.xml b/app/src/main/res/layout/nav_list.xml index 96e5371b..2c328b6f 100644 --- a/app/src/main/res/layout/nav_list.xml +++ b/app/src/main/res/layout/nav_list.xml @@ -73,11 +73,4 @@ android:scrollbarStyle="outsideOverlay" tools:listitem="@layout/nav_listitem" /> - - diff --git a/app/src/main/res/menu/episodes.xml b/app/src/main/res/menu/episodes.xml index 4c8e0dbb..23e166dc 100644 --- a/app/src/main/res/menu/episodes.xml +++ b/app/src/main/res/menu/episodes.xml @@ -9,19 +9,6 @@ custom:showAsAction="always" android:title="@string/search_label"/> - - - - + + + + diff --git a/app/src/play/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt b/app/src/play/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt index 4a2f2ff5..d61e4c6c 100644 --- a/app/src/play/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt +++ b/app/src/play/java/ac/mdiq/podcini/playback/cast/CastPsmp.kt @@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.RemoteMedia +import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.event.PlayerErrorEvent import ac.mdiq.podcini.util.event.playback.BufferUpdateEvent import android.annotation.SuppressLint @@ -107,9 +108,9 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas private fun onRemoteMediaPlayerStatusUpdated() { val mediaStatus = remoteMediaClient!!.mediaStatus if (mediaStatus == null) { - Log.d(TAG, "Received null MediaStatus") + Logd(TAG, "Received null MediaStatus") return - } else Log.d(TAG, "Received remote status/media update. New state=" + mediaStatus.playerState) + } else Logd(TAG, "Received remote status/media update. New state=" + mediaStatus.playerState) var state = mediaStatus.playerState val oldState = remoteState @@ -117,7 +118,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas val mediaChanged = !CastUtils.matches(remoteMedia, media) var stateChanged = state != oldState if (!mediaChanged && !stateChanged) { - Log.d(TAG, "Both media and state haven't changed, so nothing to do") + Logd(TAG, "Both media and state haven't changed, so nothing to do") return } val currentMedia = if (mediaChanged) localVersion(remoteMedia) else media @@ -220,7 +221,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas } override fun playMediaObject(playable: Playable, stream: Boolean, startWhenPrepared: Boolean, prepareImmediately: Boolean) { - Log.d(TAG, "playMediaObject() called") + Logd(TAG, "playMediaObject() called") playMediaObject(playable, false, stream, startWhenPrepared, prepareImmediately) } @@ -233,7 +234,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas */ private fun playMediaObject(playable: Playable, forceReset: Boolean, stream: Boolean, startWhenPrepared: Boolean, prepareImmediately: Boolean) { if (!CastUtils.isCastable(playable, castContext.sessionManager.currentCastSession)) { - Log.d(TAG, "media provided is not compatible with cast device") + Logd(TAG, "media provided is not compatible with cast device") EventBus.getDefault().post(PlayerErrorEvent("Media not compatible with cast device")) var nextPlayable: Playable? = playable do { @@ -248,7 +249,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas if (media != null) { if (!forceReset && media!!.getIdentifier() == playable.getIdentifier() && status == PlayerStatus.PLAYING) { // episode is already playing -> ignore method call - Log.d(TAG, "Method call to playMediaObject was ignored: media file already playing.") + Logd(TAG, "Method call to playMediaObject was ignored: media file already playing.") return } else { // set temporarily to pause in order to update list with current position @@ -287,7 +288,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas override fun prepare() { if (status == PlayerStatus.INITIALIZED) { - Log.d(TAG, "Preparing media player") + Logd(TAG, "Preparing media player") setPlayerStatus(PlayerStatus.PREPARING, media) var position = media!!.getPosition() if (position > 0) position = calculatePositionWithRewind(position, media!!.getLastPlayedTime()) @@ -300,9 +301,9 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas } override fun reinit() { - Log.d(TAG, "reinit() called") + Logd(TAG, "reinit() called") if (media != null) playMediaObject(media!!, true, stream = false, startWhenPrepared = startWhenPrepared.get(), prepareImmediately = false) - else Log.d(TAG, "Call to reinit was ignored: media was null") + else Logd(TAG, "Call to reinit was ignored: media was null") } override fun seekTo(t: Int) { @@ -347,7 +348,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas } override fun setVolume(volumeLeft: Float, volumeRight: Float) { - Log.d(TAG, "Setting the Stream volume on Remote Media Player") + Logd(TAG, "Setting the Stream volume on Remote Media Player") remoteMediaClient!!.setStreamVolume(volumeLeft.toDouble()) } @@ -398,7 +399,7 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas } override fun endPlayback(hasEnded: Boolean, wasSkipped: Boolean, shouldContinue: Boolean, toStoppedState: Boolean) { - Log.d(TAG, "endPlayback() called") + Logd(TAG, "endPlayback() called") val isPlaying = status == PlayerStatus.PLAYING if (status != PlayerStatus.INDETERMINATE) setPlayerStatus(PlayerStatus.INDETERMINATE, media) @@ -414,9 +415,9 @@ class CastPsmp(context: Context, callback: MediaPlayerCallback) : MediaPlayerBas val playNextEpisode = isPlaying && nextMedia != null when { - playNextEpisode -> Log.d(TAG, "Playback of next episode will start immediately.") - nextMedia == null -> Log.d(TAG, "No more episodes available to play") - else -> Log.d(TAG, "Loading next episode, but not playing automatically.") + playNextEpisode -> Logd(TAG, "Playback of next episode will start immediately.") + nextMedia == null -> Logd(TAG, "No more episodes available to play") + else -> Logd(TAG, "Loading next episode, but not playing automatically.") } if (nextMedia != null) { diff --git a/app/src/test/java/ac/mdiq/podcini/storage/DbCleanupTests.kt b/app/src/test/java/ac/mdiq/podcini/storage/DbCleanupTests.kt index 2725c133..657edd46 100644 --- a/app/src/test/java/ac/mdiq/podcini/storage/DbCleanupTests.kt +++ b/app/src/test/java/ac/mdiq/podcini/storage/DbCleanupTests.kt @@ -61,8 +61,7 @@ open class DbCleanupTests { adapter.open() adapter.close() - val prefEdit = PreferenceManager - .getDefaultSharedPreferences(context.applicationContext).edit() + val prefEdit = PreferenceManager.getDefaultSharedPreferences(context.applicationContext).edit() prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, EPISODE_CACHE_SIZE.toString()) prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP, cleanupAlgorithm.toString()) prefEdit.putBoolean(UserPreferences.PREF_ENABLE_AUTODL, true) @@ -113,10 +112,7 @@ open class DbCleanupTests { } @Throws(IOException::class) - fun populateItems(numItems: Int, feed: Feed, items: MutableList, - files: MutableList, itemState: Int, addToQueue: Boolean, - addToFavorites: Boolean - ) { + fun populateItems(numItems: Int, feed: Feed, items: MutableList, files: MutableList, itemState: Int, addToQueue: Boolean, addToFavorites: Boolean) { for (i in 0 until numItems) { val itemDate = Date((numItems - i).toLong()) var playbackCompletionDate: Date? = null @@ -136,12 +132,8 @@ open class DbCleanupTests { val adapter = getInstance() adapter.open() adapter.setCompleteFeed(feed) - if (addToQueue) { - adapter.setQueue(items) - } - if (addToFavorites) { - adapter.setFavorites(items) - } + if (addToQueue) adapter.setQueue(items) + if (addToFavorites) adapter.setFavorites(items) adapter.close() Assert.assertTrue(feed.id != 0L) diff --git a/app/src/test/java/ac/mdiq/podcini/storage/DbWriterTest.kt b/app/src/test/java/ac/mdiq/podcini/storage/DbWriterTest.kt index 0b5fecd5..2993313b 100644 --- a/app/src/test/java/ac/mdiq/podcini/storage/DbWriterTest.kt +++ b/app/src/test/java/ac/mdiq/podcini/storage/DbWriterTest.kt @@ -36,6 +36,7 @@ import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation import ac.mdiq.podcini.preferences.UserPreferences.shouldDeleteRemoveFromQueue +import ac.mdiq.podcini.util.Logd import org.awaitility.Awaitility import org.junit.After import org.junit.Assert @@ -465,11 +466,8 @@ class DbWriterTest { for (i in 0 until feed.items.size) { val feedItem: FeedItem = feed.items[i] val c = adapter.getFeedItemCursor(feedItem.id.toString()) - if (i < 2) { - Assert.assertEquals(0, c.count.toLong()) - } else { - Assert.assertEquals(1, c.count.toLong()) - } + if (i < 2) Assert.assertEquals(0, c.count.toLong()) + else Assert.assertEquals(1, c.count.toLong()) c.close() } adapter.close() @@ -699,22 +697,16 @@ class DbWriterTest { // Use array rather than List to make codes more succinct val itemIds = toItemIds(feed.items).toTypedArray() - removeQueueItem(context, false, - itemIds[1], itemIds[3])[TIMEOUT, TimeUnit.SECONDS] - assertQueueByItemIds("Average case - 2 items removed successfully", - itemIds[0], itemIds[2]) + removeQueueItem(context, false, itemIds[1], itemIds[3])[TIMEOUT, TimeUnit.SECONDS] + assertQueueByItemIds("Average case - 2 items removed successfully", itemIds[0], itemIds[2]) removeQueueItem(context, false)[TIMEOUT, TimeUnit.SECONDS] - assertQueueByItemIds("Boundary case - no items supplied. queue should see no change", - itemIds[0], itemIds[2]) + assertQueueByItemIds("Boundary case - no items supplied. queue should see no change", itemIds[0], itemIds[2]) - removeQueueItem(context, false, - itemIds[0], itemIds[4], -1L)[TIMEOUT, TimeUnit.SECONDS] - assertQueueByItemIds("Boundary case - items not in queue ignored", - itemIds[2]) + removeQueueItem(context, false, itemIds[0], itemIds[4], -1L)[TIMEOUT, TimeUnit.SECONDS] + assertQueueByItemIds("Boundary case - items not in queue ignored", itemIds[2]) - removeQueueItem(context, false, - itemIds[2], -1L)[TIMEOUT, TimeUnit.SECONDS] + removeQueueItem(context, false, itemIds[2], -1L)[TIMEOUT, TimeUnit.SECONDS] assertQueueByItemIds("Boundary case - invalid itemIds ignored") // the queue is empty } @@ -741,10 +733,9 @@ class DbWriterTest { } for (from in 0 until numItems) { for (to in 0 until numItems) { - if (from == to) { - continue - } - Log.d(TAG, String.format(Locale.US, "testMoveQueueItem: From=%d, To=%d", from, to)) + if (from == to) continue + + Logd(TAG, String.format(Locale.US, "testMoveQueueItem: From=%d, To=%d", from, to)) val fromID: Long = feed.items[from].id adapter = getInstance() @@ -775,8 +766,7 @@ class DbWriterTest { val feed = Feed("url", null, "title") feed.items = mutableListOf() for (i in 0 until numItems) { - val item = FeedItem(0, "title $i", "id $i", "link $i", - Date(), FeedItem.NEW, feed) + val item = FeedItem(0, "title $i", "id $i", "link $i", Date(), FeedItem.NEW, feed) item.setMedia(FeedMedia(item, "", 0, "")) feed.items.add(item) } @@ -807,8 +797,7 @@ class DbWriterTest { val feed = Feed("url", null, "title") feed.items = mutableListOf() for (i in 0 until numItems) { - val item = FeedItem(0, "title $i", "id $i", "link $i", - Date(), FeedItem.PLAYED, feed) + val item = FeedItem(0, "title $i", "id $i", "link $i", Date(), FeedItem.PLAYED, feed) item.setMedia(FeedMedia(item, "", 0, "")) feed.items.add(item) } diff --git a/build.gradle b/build.gradle index ab522b35..ebfc59ae 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ buildscript { mavenCentral() gradlePluginPortal() } - ext.kotlin_version = '1.9.23' + ext.kotlin_version = '1.9.24' dependencies { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.android.tools.build:gradle:8.2.2' diff --git a/changelog.md b/changelog.md index 138206a8..10e49142 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,15 @@ +## 5.2.0 + +* suppressed log.d messages in release app (unbelievable incomplete suppression last time) +* made loadAdditionalFeedItemListData of DBReader a bit more efficient +* reduced unnecessary loading of media info in AudioPlayer +* tuned playback controller for efficiency +* improved player details view and corrected issue of showing wrong info +* tuned episode info view for efficiency +* tuned feed info view for start efficiency +* tuned and slimmed down the drawer: number of Subscriptions now shows the number of subscriptions +* replaced many RxJava stuff with Kotlin Coroutines + ## 5.1.0 * properly destroys WebView objects diff --git a/fastlane/metadata/android/en-US/changelogs/3020143.txt b/fastlane/metadata/android/en-US/changelogs/3020143.txt new file mode 100644 index 00000000..259bf15d --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020143.txt @@ -0,0 +1,12 @@ + +Version 5.2.0 brings several changes: + +* suppressed log.d messages in release app (unbelievable incomplete suppression last time) +* made loadAdditionalFeedItemListData of DBReader a bit more efficient +* reduced unnecessary loading of media info in AudioPlayer +* tuned playback controller for efficiency +* improved player details view and corrected issue of showing wrong info +* tuned episode info view for efficiency +* tuned feed info view for start efficiency +* tuned and slimmed down the drawer: number of Subscriptions now shows the number of subscriptions +* replaced many RxJava stuff with Kotlin Coroutines