5.2.0 commit

This commit is contained in:
Xilin Jia 2024-05-15 20:00:23 +01:00
parent a8838161b4
commit 6d22cf55b9
103 changed files with 1886 additions and 1659 deletions

View File

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

View File

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

View File

@ -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<Chapter>?, chapters2: List<Chapter>?): List<Chapter>? {
Log.d(TAG, "Merging chapters")
Logd(TAG, "Merging chapters")
when {
chapters1 == null -> return chapters2
chapters2 == null -> return chapters1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,8 +12,7 @@ class FyydPodcastSearcher : PodcastSearcher {
override fun search(query: String): Single<List<PodcastSearchResult?>?> {
return Single.create { subscriber: SingleEmitter<List<PodcastSearchResult?>?> ->
val response = client.searchPodcasts(
query, 10)
val response = client.searchPodcasts(query, 10)
.subscribeOn(Schedulers.io())
.blockingGet()
val searchResults = ArrayList<PodcastSearchResult?>()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<FeedHandle
var result: FeedHandlerResult? = null
try {
result = feedHandler.parseFeed(feed)
Log.d(TAG, feed.title + " parsed")
Logd(TAG, feed.title + " parsed")
checkFeedData(feed)
// TODO: what the shit is this??
if (feed.imageUrl.isNullOrEmpty()) feed.imageUrl = Feed.PREFIX_GENERATIVE_COVER + feed.download_url
@ -78,7 +79,7 @@ class FeedParserTask(private val request: DownloadRequest) : Callable<FeedHandle
val feedFile = File(request.destination?:"junk")
if (feedFile.exists()) {
val deleted = feedFile.delete()
Log.d(TAG, "Deletion of file '" + feedFile.absolutePath + "' " + (if (deleted) "successful" else "FAILED"))
Logd(TAG, "Deletion of file '" + feedFile.absolutePath + "' " + (if (deleted) "successful" else "FAILED"))
}
}

View File

@ -29,6 +29,7 @@ import ac.mdiq.podcini.storage.DBWriter.removeQueueItem
import ac.mdiq.podcini.storage.model.feed.*
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.LongList
import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent
import ac.mdiq.podcini.util.event.MessageEvent
@ -52,7 +53,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
protected val synchronizationQueueStorage = SynchronizationQueueStorage(context)
@UnstableApi override fun doWork(): Result {
Log.d(TAG, "doWork() called")
Logd(TAG, "doWork() called")
val activeSyncProvider = getActiveSyncProvider() ?: return Result.failure()
SynchronizationSettings.updateLastSynchronizationAttempt()
@ -94,7 +95,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
@UnstableApi @Throws(SyncServiceException::class)
private fun syncSubscriptions(syncServiceImpl: ISyncService) {
Log.d(TAG, "syncSubscriptions called")
Logd(TAG, "syncSubscriptions called")
val lastSync = SynchronizationSettings.lastSubscriptionSynchronizationTimestamp
EventBus.getDefault().postSticky(SyncServiceEvent(R.string.sync_status_subscriptions))
val localSubscriptions: List<String> = getFeedListDownloadUrls()
@ -104,11 +105,11 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
val queuedRemovedFeeds: MutableList<String> = synchronizationQueueStorage.queuedRemovedFeeds
var queuedAddedFeeds: List<String> = 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<EpisodeAction>) {
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)) {

View File

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

View File

@ -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<EpisodeAction>()
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<String>, removed: List<String>): 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<EpisodeAction> = 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<FeedItem>()
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)
}

View File

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

View File

@ -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,12 +669,21 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
createStaticPlayer(context)
}
playbackParameters = exoPlayer!!.playbackParameters
bufferingUpdateDisposable = Observable.interval(bufferUpdateInterval, TimeUnit.SECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
// 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) {
releaseWifiLockIfNecessary()

View File

@ -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<List<MediaSessionCompat.QueueItem>?> ->
// val queueItems: MutableList<MediaSessionCompat.QueueItem> = 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<List<MediaItem>>) {
// 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<MediaBrowserCompat.MediaItem>? {
// val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = 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<FeedItem?>
// 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
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ loadedPlayable: Playable? -> startPlaying(loadedPlayable, allowStreamThisTime) },
{ error: Throwable ->
withContext(Dispatchers.Main) {
startPlaying(loadedPlayable, allowStreamThisTime)
}
} catch (e: Throwable) {
Logd(TAG, "Playable was not found. Stopping service.")
error.printStackTrace()
})
e.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 ->
// 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.")
error.printStackTrace()
})
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))
}
}

View File

@ -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,23 +192,34 @@ 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()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ callback.onChapterLoaded(media) },
{ throwable: Throwable? ->
Log.d(TAG, "Error loading chapters: " + Log.getStackTraceString(throwable))
})
}
}
// 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)}")
}
}
}
}
/**
* 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<FeedItem>
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<FeedItem> = 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")
}
}

View File

@ -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<Feed> = mutableListOf()
private var tags: MutableList<String> = mutableListOf()
private val feeds: MutableList<Feed> = mutableListOf()
private val feedIndex: MutableMap<Long, Feed> = mutableMapOf()
private val tags: MutableList<String> = 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<FeedItem>) {
loadTagsOfFeedItemList(items)
synchronized(feedListLock) {
loadFeedDataOfFeedItemList(items)
}
}
private fun loadTagsOfFeedItemList(items: List<FeedItem>) {
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<FeedItem>) {
Logd(TAG, "loadFeedDataOfFeedItemList called")
val feedIndex: MutableMap<Long, Feed> = ArrayMap(feeds.size)
val feedsCopy = ArrayList(feeds)
for (feed in feedsCopy) {
feedIndex[feed.id] = feed
}
// feedIndex is now a static member
// val feedIndex: MutableMap<Long, Feed> = 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<FeedItem> {
// Log.d(TAG, "getFeedItemList() called with: feed = [$feed]")
fun getFeedItemList(feed: Feed?, filter_: FeedItemFilter?, sortOrder: SortOrder?): List<FeedItem> {
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<Long> {
Logd(TAG, "getQueueIDSet() called")
val adapter = getInstance()
adapter.open()
try {
adapter.queueIDCursor.use { cursor ->
val queueIds = HashSet<Long>(cursor.count)
while (cursor.moveToNext()) {
queueIds.add(cursor.getLong(0))
}
return queueIds
}
// return HashSet()
} finally {
adapter.close()
}
}
@JvmStatic
fun getQueue(): List<FeedItem> {
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<Long> {
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<Long>(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<FeedItem> {
fun getEpisodes(offset: Int, limit: Int, filter_: FeedItemFilter?, sortOrder: SortOrder?): List<FeedItem> {
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<Chapter>? {
adapter?.getSimpleChaptersOfFeedItemCursor(item)?.use { cursor ->
private fun loadChaptersOfFeedItem(adapter: PodDBAdapter, item: FeedItem): List<Chapter>? {
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<Long, Int> = adapter.getFeedCounters(feedCounterSetting)
val feedCounters: Map<Long, Int> = 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<FeedDrawerItem> = 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
}

View File

@ -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<FeedItem> = 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<List<FeedItem>> {
Log.d(TAG, "searchFeedItems called")
Logd(TAG, "searchFeedItems called")
return FutureTask(object : QueryTask<List<FeedItem>>() {
override fun execute(adapter: PodDBAdapter?) {
val searchResult = adapter?.searchItems(feedID, query)

View File

@ -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<FeedItem>): 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<FeedItem?>, 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<String>): 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()

View File

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

View File

@ -7,8 +7,10 @@ class NavDrawerData(@JvmField val items: List<FeedDrawerItem>,
@JvmField val numNewItems: Int,
val numDownloadedItems: Int,
val feedCounters: Map<Long, Int>,
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

View File

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

View File

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

View File

@ -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<String?>?) {
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<Long, Int> {
fun getFeedEpisodesCounters(setting: FeedCounter?, vararg feedIds: Long): Map<Long, Int> {
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<Long, Int> {
private fun conditionalFeedEpisodesCounterRead(whereRead: String, vararg feedIds: Long): Map<Long, Int> {
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<Long, Int> {
val whereRead = KEY_READ + "=" + FeedItem.PLAYED
return conditionalFeedCounterRead(whereRead, *feedIds)
return conditionalFeedEpisodesCounterRead(whereRead, *feedIds)
}
val mostRecentItemDates: Map<Long, Long>
@ -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

View File

@ -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<Feed?>?, 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")
}
/**

View File

@ -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<Feed?>?, 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</a></span></p></div></li>\n")
}
writer.append(templateParts[1])
Log.d(TAG, "Finished writing document")
Logd(TAG, "Finished writing document")
}
override fun fileExtension(): String {

View File

@ -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<Feed?>?, 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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<Feed> = 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<Bitmap?> {
// @UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap?>, isFirstResource: Boolean): Boolean {
// addShortcut(feed, null)
// return true
// }
// @UnstableApi override fun onResourceReady(resource: Bitmap, model: Any, target: Target<Bitmap?>, 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 {
// disposable = Observable.fromCallable {
// val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
// getFeedItems(data.items, ArrayList())
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<Feed> ->
// listItems = result
// val titles = ArrayList<String>()
// for (feed in result) {
// if (feed.title != null) titles.add(feed.title!!)
// }
// val adapter: ArrayAdapter<String> = ArrayAdapter<String>(this, R.layout.simple_list_item_multiple_choice_on_start, titles)
// binding.list.adapter = adapter
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
scope.launch {
try {
val result = withContext(Dispatchers.IO) {
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
getFeedItems(data.items, ArrayList())
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<Feed> ->
withContext(Dispatchers.Main) {
listItems = result
val titles = ArrayList<String>()
for (feed in result) {
if (feed.title != null) titles.add(feed.title!!)
}
val adapter: ArrayAdapter<String> = ArrayAdapter<String>(this, R.layout.simple_list_item_multiple_choice_on_start, titles)
val adapter: ArrayAdapter<String> = ArrayAdapter<String>(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() {

View File

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

View File

@ -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<String> = 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<String?> = 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,71 +200,14 @@ 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
}
}
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)
@ -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"
}
}

View File

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

View File

@ -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 {
// 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()
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
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()

View File

@ -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 {
// Observable.fromCallable {
// DBReader.getTags()
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<String> ->
// val acAdapter = ArrayAdapter(requireContext(), R.layout.single_tag_text_view, result)
// binding.newTagEditText.setAdapter(acAdapter)
// }, { error: Throwable? ->
// Log.e(TAG, Log.getStackTraceString(error))
// })
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
try {
val result = withContext(Dispatchers.IO) {
DBReader.getTags()
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<String> ->
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) {

View File

@ -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<Feed> { addLocalFolder(uri) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ feed: Feed ->
// Observable.fromCallable<Feed> { addLocalFolder(uri) }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { feed: Feed ->
// val fragment: Fragment = FeedItemlistFragment.newInstance(feed.id)
// (getActivity() as MainActivity).loadChildFragment(fragment)
// }, { error: Throwable ->
// Log.e(TAG, Log.getStackTraceString(error))
// (getActivity() as MainActivity)
// .showSnackbarAbovePlayer(error.localizedMessage, Snackbar.LENGTH_LONG)
// })
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
try {
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)
}, { error: Throwable ->
Log.e(TAG, Log.getStackTraceString(error))
(getActivity() as MainActivity)
.showSnackbarAbovePlayer(error.localizedMessage, Snackbar.LENGTH_LONG)
})
}
}
} 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? {

View File

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

View File

@ -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<Playable> { emitter: MaybeEmitter<Playable?> ->
// val media: Playable? = theMedia
// if (media != null) {
// if (includingChapters) ChapterUtils.loadChapters(media, requireContext(), false)
// emitter.onSuccess(media)
// } else emitter.onComplete()
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ media: Playable ->
// currentMedia = media
// updateUi(media)
// playerFragment1?.updateUi(media)
// playerFragment2?.updateUi(media)
// if (!includingChapters) loadMediaInfo(true)
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
// { updateUi(null) })
// }
// }
fun loadMediaInfo(includingChapters: Boolean) {
val theMedia = controller?.getMedia() ?: return
Logd(TAG, "loadMediaInfo $theMedia")
if (currentMedia == null || theMedia.getIdentifier() != currentMedia?.getIdentifier()) {
Logd(TAG, "loadMediaInfo loading details")
disposable?.dispose()
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
val media: Playable? = theMedia
if (media != null) {
if (includingChapters) ChapterUtils.loadChapters(media, requireContext(), false)
emitter.onSuccess(media)
} else emitter.onComplete()
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)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ media: Playable ->
currentMedia = media
updateUi(media)
playerFragment1?.updateUi(media)
playerFragment2?.updateUi(media)
updateUi()
playerFragment1?.updateUi(currentMedia)
playerFragment2?.updateUi(currentMedia)
if (!includingChapters) loadMediaInfo(true)
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
{ updateUi(null) })
}.invokeOnCompletion { throwable ->
if (throwable!= null) {
Log.e(TAG, Log.getStackTraceString(throwable))
}
}
}
}
@ -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())

View File

@ -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<FeedItem> = 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,7 +240,26 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
@UnstableApi private fun performMultiSelectAction(actionItemId: Int) {
val handler = EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId)
Completable.fromAction {
// Completable.fromAction {
// handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>())
// if (listAdapter.shouldSelectLazyLoadedItems()) {
// var applyPage = page + 1
// var nextPage: List<FeedItem>
// do {
// nextPage = loadMoreData(applyPage)
// handler.handleAction(nextPage)
// applyPage++
// } while (nextPage.size == EPISODES_PER_PAGE)
// }
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ listAdapter.endSelectMode() },
// { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
scope.launch {
try {
withContext(Dispatchers.IO) {
handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>())
if (listAdapter.shouldSelectLazyLoadedItems()) {
var applyPage = page + 1
@ -253,57 +270,85 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
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<FeedItem> ->
// disposable = Observable.fromCallable { loadMoreData(page) }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ data: List<FeedItem> ->
// if (data.size < EPISODES_PER_PAGE) hasMoreItems = false
// Logd(TAG, "loadMoreItems $page ${data.size}")
// episodes.addAll(data)
// listAdapter.setDummyViews(0)
// listAdapter.updateItems(episodes)
// if (listAdapter.shouldSelectLazyLoadedItems()) listAdapter.setSelected(episodes.size - data.size, episodes.size, true)
//
// }, { error: Throwable? ->
// listAdapter.setDummyViews(0)
// listAdapter.updateItems(emptyList())
// Log.e(TAG, Log.getStackTraceString(error))
// }, {
// // Make sure to not always load 2 pages at once
// recyclerView.post { isLoadingMore = false }
// })
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)
}, { error: Throwable? ->
}
} 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 {
// disposable = Observable.fromCallable {
// Pair(loadData().toMutableList(), loadTotalItemCount())
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { data: Pair<MutableList<FeedItem>, Int> ->
// val restoreScrollPosition = episodes.isEmpty()
// episodes = data.first
// hasMoreItems = !(page == 1 && episodes.size < EPISODES_PER_PAGE)
// progressBar.visibility = View.GONE
// listAdapter.setDummyViews(0)
// listAdapter.updateItems(episodes)
// listAdapter.setTotalNumberOfItems(data.second)
// if (restoreScrollPosition) recyclerView.restoreScrollPosition(getPrefName())
// updateToolbar()
// }, { error: Throwable? ->
// listAdapter.setDummyViews(0)
// listAdapter.updateItems(emptyList())
// Log.e(TAG, Log.getStackTraceString(error))
// })
scope.launch {
try {
val data = withContext(Dispatchers.IO) {
Pair(loadData().toMutableList(), loadTotalItemCount())
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ data: Pair<MutableList<FeedItem>, Int> ->
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? ->
}
} catch (e: Throwable) {
listAdapter.setDummyViews(0)
listAdapter.updateItems(emptyList())
Log.e(TAG, Log.getStackTraceString(error))
})
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
protected abstract fun loadData(): List<FeedItem>
@ -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
}

View File

@ -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<PodcastSearchResult>? = null
private var topList: List<PodcastSearchResult>? = 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<PodcastSearchResult>? ->
// disposable = Observable.fromCallable { loader.loadToplist(country?:"",
// NUM_OF_TOP_PODCASTS, DBReader.getFeedList()) }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { podcasts: List<PodcastSearchResult>? ->
// progressBar.visibility = View.GONE
// topList = podcasts
// updateData(topList)
// }, { error: Throwable ->
// Log.e(TAG, Log.getStackTraceString(error))
// progressBar.visibility = View.GONE
// txtvError.text = error.message
// txtvError.visibility = View.VISIBLE
// butRetry.setOnClickListener { loadToplist(country) }
// butRetry.visibility = View.VISIBLE
// })
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))
}
} catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e))
progressBar.visibility = View.GONE
txtvError.text = error.message
txtvError.text = e.message
txtvError.visibility = View.VISIBLE
butRetry.setOnClickListener { loadToplist(country) }
butRetry.visibility = View.VISIBLE
})
}
}
}
override fun onMenuItemClick(item: MenuItem): Boolean {

View File

@ -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<DownloadResult> = 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<DownloadResult>? ->
if (result != null) {
// disposable = Observable.fromCallable { DBReader.getDownloadLog() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ result: List<DownloadResult>? ->
// if (result != null) {
// downloadLog = result
// adapter.setDownloadLog(downloadLog)
// }
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
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 {

View File

@ -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 {
// disposable = Observable.fromCallable {
// val sortOrder: SortOrder? = UserPreferences.downloadsSortedOrder
// val downloadedItems: List<FeedItem> = DBReader.getEpisodes(0, Int.MAX_VALUE,
// FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)
//
// val mediaUrls: MutableList<String> = ArrayList()
// if (runningDownloads.isEmpty()) return@fromCallable downloadedItems
//
// for (url in runningDownloads) {
// if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) continue // Already in list
// mediaUrls.add(url)
// }
// val currentDownloads: MutableList<FeedItem> = DBReader.getFeedItemsWithUrl(mediaUrls).toMutableList()
// currentDownloads.addAll(downloadedItems)
// currentDownloads
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<FeedItem> ->
// items = result.toMutableList()
// adapter.setDummyViews(0)
// progressBar.visibility = View.GONE
// adapter.updateItems(result)
// refreshInfoBar()
// }, { error: Throwable? ->
// adapter.setDummyViews(0)
// adapter.updateItems(emptyList())
// Log.e(TAG, Log.getStackTraceString(error))
// })
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
try {
val result = withContext(Dispatchers.IO) {
val sortOrder: SortOrder? = UserPreferences.downloadsSortedOrder
val downloadedItems: List<FeedItem> = DBReader.getEpisodes(0, Int.MAX_VALUE,
FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)
val downloadedItems: List<FeedItem> = DBReader.getEpisodes(0, Int.MAX_VALUE, FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)
val mediaUrls: MutableList<String> = ArrayList()
if (runningDownloads.isEmpty()) return@fromCallable downloadedItems
if (runningDownloads.isEmpty()) {
downloadedItems
} else {
for (url in runningDownloads) {
if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) continue // Already in list
if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) continue
mediaUrls.add(url)
}
val currentDownloads: MutableList<FeedItem> = DBReader.getFeedItemsWithUrl(mediaUrls).toMutableList()
currentDownloads.addAll(downloadedItems)
currentDownloads
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<FeedItem> ->
}
withContext(Dispatchers.Main) {
items = result.toMutableList()
adapter.setDummyViews(0)
progressBar.visibility = View.GONE
adapter.updateItems(result)
refreshInfoBar()
}, { error: Throwable? ->
}
} catch (e: Throwable) {
adapter.setDummyViews(0)
adapter.updateItems(emptyList())
Log.e(TAG, Log.getStackTraceString(error))
})
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
private fun refreshInfoBar() {

View File

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

View File

@ -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<FeedItem?> { 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<FeedItem?> { this.loadInBackground() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: FeedItem? ->
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
},
{ 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()
} 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
}
}

View File

@ -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<Uri?, Uri>(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<Uri?, Uri>(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,22 +143,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val feedId = requireArguments().getLong(EXTRA_FEED_ID)
disposable = Maybe.create { emitter: MaybeEmitter<Feed?> ->
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)) }, {})
return binding.root
}
override fun onConfigurationChanged(newConfig: Configuration) {
@ -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)
// 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)
}
.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) })
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)
}
}
}
}
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
}
}

View File

@ -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<DownloadResult>(
Callable {
// Maybe.fromCallable<DownloadResult>(
// Callable {
// val feedDownloadLog: List<DownloadResult> = DBReader.getFeedDownloadLog(feedID)
// if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) return@Callable null
// feedDownloadLog[0]
// })
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { downloadStatus: DownloadResult ->
// DownloadLogDetailsDialog(requireContext(), downloadStatus).show()
// },
// { error: Throwable -> error.printStackTrace() },
// { DownloadLogFragment().show(childFragmentManager, null) })
scope.launch {
val downloadResult = withContext(Dispatchers.IO) {
val feedDownloadLog: List<DownloadResult> = DBReader.getFeedDownloadLog(feedID)
if (feedDownloadLog.isEmpty() || feedDownloadLog[0].isSuccessful) return@Callable null
feedDownloadLog[0]
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ downloadStatus: DownloadResult ->
DownloadLogDetailsDialog(requireContext(), downloadStatus).show()
},
{ error: Throwable -> error.printStackTrace() },
{ DownloadLogFragment().show(childFragmentManager, null) })
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<Feed?> { 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<Feed?> { this.loadData() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: Feed? ->
// feed = result
// Logd(TAG, "loadItems subscribe called ${feed?.title}")
// if (feed != null) {
// var hasNonMediaItems = false
// for (item in feed!!.items) {
// if (item.media == null) {
// hasNonMediaItems = true
// break
// }
// }
// if (hasNonMediaItems) {
// ioScope.launch {
// if (!ttsReady) {
// initializeTTS(requireContext())
// semaphore.acquire()
// }
// }
// }
// }
// swipeActions.setFilter(feed?.itemFilter)
// refreshHeaderView()
// binding.progressBar.visibility = View.GONE
// adapter.setDummyViews(0)
// if (feed != null) adapter.updateItems(feed!!.items)
// binding.header.counts.text = (feed?.items?.size?:0).toString()
// updateToolbar()
// }, { error: Throwable? ->
// feed = null
// refreshHeaderView()
// adapter.setDummyViews(0)
// adapter.updateItems(emptyList())
// updateToolbar()
// Log.e(TAG, Log.getStackTraceString(error))
// })
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? ->
}
} catch (e: Throwable) {
feed = null
refreshHeaderView()
adapter.setDummyViews(0)
adapter.updateItems(emptyList())
updateToolbar()
Log.e(TAG, Log.getStackTraceString(error))
})
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
private fun loadData(): Feed? {

View File

@ -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<Feed> ->
val feed = DBReader.getFeed(feedId)
if (feed != null) emitter.onSuccess(feed)
else emitter.onComplete()
} as MaybeOnSubscribe<Feed>)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: Feed -> toolbar.subtitle = result.title },
{ error: Throwable? -> Log.d(TAG, Log.getStackTraceString(error)) },
{})
// disposable = Maybe.create(MaybeOnSubscribe { emitter: MaybeEmitter<Feed> ->
// val feed = DBReader.getFeed(feedId)
// if (feed != null) emitter.onSuccess(feed)
// else emitter.onComplete()
// } as MaybeOnSubscribe<Feed>)
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ result: Feed -> toolbar.subtitle = result.title },
// { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) },
// {})
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,15 +137,48 @@ class FeedSettingsFragment : Fragment() {
findPreference<Preference>(PREF_SCREEN)!!.isVisible = false
val feedId = requireArguments().getLong(EXTRA_FEED_ID)
disposable = Maybe.create { emitter: MaybeEmitter<Feed?> ->
val feed = DBReader.getFeed(feedId)
if (feed != null) emitter.onSuccess(feed)
else emitter.onComplete()
// disposable = Maybe.create { emitter: MaybeEmitter<Feed?> ->
// val feed = DBReader.getFeed(feedId)
// if (feed != null) emitter.onSuccess(feed)
// else emitter.onComplete()
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ result: Feed? ->
// feed = result
// feedPreferences = feed!!.preferences
//
// setupAutoDownloadGlobalPreference()
// setupAutoDownloadPreference()
// setupKeepUpdatedPreference()
// setupAutoDeletePreference()
// setupVolumeAdaptationPreferences()
//// setupNewEpisodesAction()
// setupAuthentificationPreference()
// setupEpisodeFilterPreference()
// setupPlaybackSpeedPreference()
// setupFeedAutoSkipPreference()
//// setupEpisodeNotificationPreference()
// setupTags()
//
// updateAutoDeleteSummary()
// updateVolumeAdaptationValue()
// updateAutoDownloadEnabled()
//// updateNewEpisodesAction()
//
// if (feed!!.isLocalFeed) {
// findPreference<Preference>(PREF_AUTHENTICATION)!!.isVisible = false
// findPreference<Preference>(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false
// }
// findPreference<Preference>(PREF_SCREEN)!!.isVisible = true
// }, { error: Throwable? -> Logd(TAG, Log.getStackTraceString(error)) }, {})
scope.launch {
feed = withContext(Dispatchers.IO) {
DBReader.getFeed(feedId)
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: Feed? ->
feed = result
if (feed!= null) {
withContext(Dispatchers.Main) {
feedPreferences = feed!!.preferences
setupAutoDownloadGlobalPreference()
@ -139,30 +186,34 @@ class FeedSettingsFragment : Fragment() {
setupKeepUpdatedPreference()
setupAutoDeletePreference()
setupVolumeAdaptationPreferences()
// setupNewEpisodesAction()
setupAuthentificationPreference()
setupEpisodeFilterPreference()
setupPlaybackSpeedPreference()
setupFeedAutoSkipPreference()
// setupEpisodeNotificationPreference()
setupTags()
updateAutoDeleteSummary()
updateVolumeAdaptationValue()
updateAutoDownloadEnabled()
// updateNewEpisodesAction()
if (feed!!.isLocalFeed) {
findPreference<Preference>(PREF_AUTHENTICATION)!!.isVisible = false
findPreference<Preference>(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false
}
findPreference<Preference>(PREF_SCREEN)!!.isVisible = true
}, { error: Throwable? -> 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() {

View File

@ -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,11 +56,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
private var navDrawerData: NavDrawerData? = null
private var flatItemList: List<NavDrawerData.FeedDrawerItem>? = 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<String> = HashSet()
@ -70,7 +66,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
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<String>())) // Must not modify
openFolders = HashSet(preferences.getStringSet(PREF_OPEN_FOLDERS, HashSet<String>())!!) // 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 {
scope.launch {
try {
val result = withContext(Dispatchers.IO) {
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
Pair(data, makeFlatDrawerData(data.items, 0))
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: Pair<NavDrawerData, List<NavDrawerData.FeedDrawerItem>> ->
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<NavDrawerData.FeedDrawerItem>, layer: Int): List<NavDrawerData.FeedDrawerItem> {
@ -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
}
}

View File

@ -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 {
// 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
}
.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)) })
withContext(Dispatchers.Main) {
if (request.destination != null) checkDownloadResult(status, request.destination)
}
} catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
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<Feed>? ->
// updater = Observable.fromCallable { DBReader.getFeedList() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { feeds: List<Feed>? ->
// this@OnlineFeedViewFragment.feeds = feeds
// handleUpdatedFeedStatus()
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }
// )
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<FeedHandlerResult?>() {
@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<FeedHandlerResult?>() {
// @UnstableApi override fun onSuccess(result: FeedHandlerResult) {
// showFeedInformation(result.feed, result.alternateFeedUrls)
// }
//
// override fun onComplete() {
// // Ignore null result: We showed the discovery dialog.
// }
//
// override fun onError(error: Throwable) {
// showErrorDialog(error.message, "")
// Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(error))
// }
// })
scope.launch {
try {
val result = withContext(Dispatchers.Default) {
doParseFeed(destination)
}
withContext(Dispatchers.Main) {
if (result != null) showFeedInformation(result.feed, result.alternateFeedUrls)
}
} catch (e: Throwable) {
withContext(Dispatchers.Main) {
showErrorDialog(e.message, "")
Logd(TAG, "Feed parser exception: " + Log.getStackTraceString(e))
}
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))
}
})
}
/**

View File

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

View File

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

View File

@ -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
@ -67,14 +63,14 @@ class PlayerDetailsFragment : Fragment() {
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<String?> ->
// if (item == null) {
// media = controller?.getMedia()
// if (media == null) {
// emitter.onComplete()
// return@create
// }
// if (media is FeedMedia) {
// val feedMedia = media as FeedMedia
// item = feedMedia.item
// item?.setDescription(null)
// showHomeText = false
// homeText = null
// }
// }
// if (item != null) {
// media = item!!.media
// if (item!!.description == null) DBReader.loadTextDetailsOfFeedItem(item!!)
// if (prevItem?.itemIdentifier != item!!.itemIdentifier) cleanedNotes = null
// if (cleanedNotes == null) {
// Logd(TAG, "calling load description ${item!!.description==null} ${item!!.title}")
// val shownotesCleaner = ShownotesCleaner(context, item?.description ?: "", media?.getDuration()?:0)
// cleanedNotes = shownotesCleaner.processShownotes()
// }
// prevItem = item
// emitter.onSuccess(cleanedNotes?:"")
// } else emitter.onComplete()
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ data: String? ->
// Logd(TAG, "subscribe: ${media?.getEpisodeTitle()}")
// displayMediaInfo(media!!)
// shownoteView.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html", "utf-8", "about:blank")
// Logd(TAG, "Webview loaded")
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
// }
private fun load() {
val context = context ?: return
webViewLoader = Maybe.create { emitter: MaybeEmitter<String?> ->
scope.launch {
withContext(Dispatchers.IO) {
if (item == null) {
media = controller?.getMedia()
if (media == null) {
emitter.onComplete()
return@create
}
if (media is FeedMedia) {
if (media != null && media is FeedMedia) {
val feedMedia = media as FeedMedia
if (item?.itemIdentifier != feedMedia.item?.itemIdentifier) {
item = feedMedia.item
item?.setDescription(null)
showHomeText = false
homeText = null
}
}
// 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()
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?:"")
}
.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()
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))
}
}
@UnstableApi private fun loadMediaInfo() {
disposable?.dispose()
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
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,15 +241,10 @@ 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) }
@ -236,11 +252,10 @@ class PlayerDetailsFragment : Fragment() {
} 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<Drawable> = 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"

View File

@ -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<FeedItem> ->
queue = items
// disposable = Observable.fromCallable { DBReader.getQueue().toMutableList() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ items: MutableList<FeedItem> ->
// queue = items
// progressBar.visibility = View.GONE
// recyclerAdapter?.setDummyViews(0)
// recyclerAdapter?.updateItems(queue)
// if (restoreScrollPosition) recyclerView.restoreScrollPosition(TAG)
// refreshInfoBar()
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
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()
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
} 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)
}
}

View File

@ -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,7 +32,8 @@ 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
@ -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 {
// disposable = Observable.fromCallable {
// loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList())
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { podcasts: List<PodcastSearchResult> ->
// errorView.visibility = View.GONE
// if (podcasts.isEmpty()) {
// errorTextView.text = resources.getText(R.string.search_status_no_results)
// errorView.visibility = View.VISIBLE
// discoverGridLayout.visibility = View.INVISIBLE
// } else {
// discoverGridLayout.visibility = View.VISIBLE
// adapter.updateData(podcasts)
// }
// }, { error: Throwable ->
// Log.e(TAG, Log.getStackTraceString(error))
// errorTextView.text = error.localizedMessage
// errorView.visibility = View.VISIBLE
// discoverGridLayout.visibility = View.INVISIBLE
// errorRetry.visibility = View.VISIBLE
// errorRetry.setOnClickListener { loadToplist() }
// })
scope.launch {
try {
val podcasts = withContext(Dispatchers.IO) {
loader.loadToplist(countryCode, NUM_SUGGESTIONS, DBReader.getFeedList())
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ podcasts: List<PodcastSearchResult> ->
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
}
} 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) {

View File

@ -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<FeedItem> = 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,17 +296,37 @@ 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<FeedItem>?, List<Feed?>?> ->
// disposable = Observable.fromCallable { this.performSearch() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ results: Pair<List<FeedItem>?, List<Feed?>?> ->
// progressBar.visibility = View.GONE
// if (results.first != null) {
// this.results = results.first!!.toMutableList()
// adapter.updateItems(results.first!!)
// }
// if (requireArguments().getLong(ARG_FEED, 0) == 0L) {
// if (results.second != null) adapterFeeds.updateData(results.second!!.filterNotNull())
// } else adapterFeeds.updateData(emptyList())
//
// if (searchView.query.toString().isEmpty()) emptyViewHandler.setMessage(R.string.type_to_search)
// else emptyViewHandler.setMessage(getString(R.string.no_results_for_query) + searchView.query)
//
// }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
scope.launch {
try {
val results = withContext(Dispatchers.IO) {
performSearch()
}
withContext(Dispatchers.Main) {
progressBar.visibility = View.GONE
if (results.first != null) {
this.results = results.first!!.toMutableList()
this@SearchFragment.results = results.first!!.toMutableList()
adapter.updateItems(results.first!!)
}
if (requireArguments().getLong(ARG_FEED, 0) == 0L) {
@ -315,8 +335,11 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
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)) })
}
} catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e))
}
}
}
@UnstableApi private fun performSearch(): Pair<List<FeedItem>?, List<Feed?>?> {

View File

@ -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<NavDrawerData.FeedDrawerItem> = mutableListOf()
private var feedListFiltered: List<NavDrawerData.FeedDrawerItem> = 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 {
// disposable = Observable.fromCallable {
// val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
// val items: List<NavDrawerData.FeedDrawerItem> = data.items
// items
// }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(
// { result: List<NavDrawerData.FeedDrawerItem> ->
// // We have fewer items. This can result in items being selected that are no longer visible.
// if ( feedListFiltered.size > result.size) subscriptionAdapter.endSelectMode()
// feedList = result
// filterOnTag()
// progressBar.visibility = View.GONE
// subscriptionAdapter.setItems(feedListFiltered)
// feedCount.text = feedListFiltered.size.toString() + " / " + feedList.size.toString()
// emptyView.updateVisibility()
// }, { error: Throwable? ->
// Log.e(TAG, Log.getStackTraceString(error))
// })
scope.launch {
try {
val result = withContext(Dispatchers.IO) {
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
val items: List<NavDrawerData.FeedDrawerItem> = data.items
items
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<NavDrawerData.FeedDrawerItem> ->
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

View File

@ -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,15 +205,35 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
}
@UnstableApi private fun load() {
disposable?.dispose()
Log.d(TAG, "load() called")
// disposable?.dispose()
Logd(TAG, "load() called")
disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: FeedItem? ->
item = result
Log.d(TAG, "load() item ${item?.id}")
// disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe({ result: FeedItem? ->
// item = result
// Logd(TAG, "load() item ${item?.id}")
// if (item != null) {
// val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE)
// if (isFavorite != isFav) {
// isFavorite = isFav
// invalidateOptionsMenu(requireActivity())
// }
// }
// onFragmentLoaded()
// itemsLoaded = true
// }, { error: Throwable? ->
// Log.e(TAG, Log.getStackTraceString(error))
// })
scope.launch {
try {
item = withContext(Dispatchers.IO) {
loadInBackground()
}
withContext(Dispatchers.Main) {
Logd(TAG, "load() item ${item?.id}")
if (item != null) {
val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE)
if (isFavorite != isFav) {
@ -221,9 +243,12 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
}
onFragmentLoaded()
itemsLoaded = true
}, { error: Throwable? ->
Log.e(TAG, Log.getStackTraceString(error))
})
}
} 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()
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -73,11 +73,4 @@
android:scrollbarStyle="outsideOverlay"
tools:listitem="@layout/nav_listitem" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
style="?android:attr/progressBarStyle" />
</RelativeLayout>

View File

@ -9,19 +9,6 @@
custom:showAsAction="always"
android:title="@string/search_label"/>
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
<item
android:id="@+id/filter_items"
android:icon="@drawable/ic_filter"
android:menuCategory="container"
android:title="@string/filter"
custom:showAsAction="ifRoom"/>
<item
android:id="@+id/action_favorites"
android:icon="@drawable/ic_star_border"
@ -29,9 +16,23 @@
android:title="@string/favorite_episodes_label"
custom:showAsAction="always"/>
<item
android:id="@+id/filter_items"
android:icon="@drawable/ic_filter"
android:menuCategory="container"
android:title="@string/filter"
custom:showAsAction="always"/>
<item
android:id="@+id/episodes_sort"
android:icon="@drawable/arrows_sort"
android:title="@string/sort"
custom:showAsAction="ifRoom" />
<item
android:id="@+id/refresh_item"
android:title="@string/refresh_label"
android:menuCategory="container"
custom:showAsAction="never" />
</menu>

View File

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

View File

@ -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<FeedItem>,
files: MutableList<File>, itemState: Int, addToQueue: Boolean,
addToFavorites: Boolean
) {
fun populateItems(numItems: Int, feed: Feed, items: MutableList<FeedItem>, files: MutableList<File>, 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)

View File

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

Some files were not shown because too many files have changed in this diff Show More