5.4.0 commit

This commit is contained in:
Xilin Jia 2024-05-19 22:31:22 +01:00
parent ae376422a4
commit 36e1823d58
139 changed files with 868 additions and 695 deletions

View File

@ -17,7 +17,7 @@ Compared to AntennaPod this project:
2. Plays in `AudioOffloadMode`, kind to device battery, 2. Plays in `AudioOffloadMode`, kind to device battery,
3. Is purely `Kotlin` based and mono-modular, 3. Is purely `Kotlin` based and mono-modular,
4. Targets Android 14 with updated dependencies, 4. Targets Android 14 with updated dependencies,
5. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava, and SharedFlow replacing EventBus, 5. Outfits with Viewbinding, Coil replacing Glide, coroutines replacing RxJava and threads, and SharedFlow replacing EventBus,
6. Boasts new UI's including streamlined drawer, subscriptions view and player controller, 6. Boasts new UI's including streamlined drawer, subscriptions view and player controller,
7. Accepts podcast as well as plain RSS and YouTube feeds, 7. Accepts podcast as well as plain RSS and YouTube feeds,
8. Offers Readability and Text-to-Speech for RSS contents, 8. Offers Readability and Text-to-Speech for RSS contents,

View File

@ -159,8 +159,8 @@ android {
// Version code schema (not used): // Version code schema (not used):
// "1.2.3-beta4" -> 1020304 // "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395 // "1.2.3" -> 1020395
versionCode 3020146 versionCode 3020147
versionName "5.3.1" versionName "5.4.0"
def commit = "" def commit = ""
try { try {

View File

@ -30,6 +30,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import de.test.podcini.EspressoTestUtils import de.test.podcini.EspressoTestUtils
import de.test.podcini.IgnoreOnCi import de.test.podcini.IgnoreOnCi
import de.test.podcini.ui.UITestUtils import de.test.podcini.ui.UITestUtils
import kotlinx.coroutines.runBlocking
import org.awaitility.Awaitility import org.awaitility.Awaitility
import org.hamcrest.Matchers import org.hamcrest.Matchers
import org.junit.* import org.junit.*
@ -166,7 +167,7 @@ class PlaybackTest {
fun testStartLocal() { fun testStartLocal() {
uiTestUtils!!.addLocalFeedData(true) uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent()) activityTestRule.launchActivity(Intent())
clearQueue().get() runBlocking { clearQueue().join() }
startLocalPlayback() startLocalPlayback()
} }
@ -175,7 +176,7 @@ class PlaybackTest {
fun testPlayingItemAddsToQueue() { fun testPlayingItemAddsToQueue() {
uiTestUtils!!.addLocalFeedData(true) uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent()) activityTestRule.launchActivity(Intent())
clearQueue().get() runBlocking { clearQueue().join() }
val queue = getQueue() val queue = getQueue()
Assert.assertEquals(0, queue.size.toLong()) Assert.assertEquals(0, queue.size.toLong())
startLocalPlayback() startLocalPlayback()
@ -188,7 +189,7 @@ class PlaybackTest {
setContinuousPlaybackPreference(false) setContinuousPlaybackPreference(false)
uiTestUtils!!.addLocalFeedData(true) uiTestUtils!!.addLocalFeedData(true)
activityTestRule.launchActivity(Intent()) activityTestRule.launchActivity(Intent())
clearQueue().get() runBlocking { clearQueue().join() }
startLocalPlayback() startLocalPlayback()
} }
@ -261,10 +262,9 @@ class PlaybackTest {
protected fun replayEpisodeCheck(followQueue: Boolean) { protected fun replayEpisodeCheck(followQueue: Boolean) {
setContinuousPlaybackPreference(followQueue) setContinuousPlaybackPreference(followQueue)
uiTestUtils!!.addLocalFeedData(true) uiTestUtils!!.addLocalFeedData(true)
clearQueue().get() runBlocking { clearQueue().join() }
activityTestRule.launchActivity(Intent()) activityTestRule.launchActivity(Intent())
val episodes = getEpisodes(0, 10, val episodes = getEpisodes(0, 10, unfiltered(), SortOrder.DATE_NEW_OLD)
unfiltered(), SortOrder.DATE_NEW_OLD)
startLocalPlayback() startLocalPlayback()
val media = episodes[0].media val media = episodes[0].media

View File

@ -147,12 +147,12 @@ class NavigationDrawerTest {
val hidden = hiddenDrawerItems val hidden = hiddenDrawerItems
Assert.assertEquals(2, hidden!!.size.toLong()) Assert.assertEquals(2, hidden!!.size.toLong())
Assert.assertTrue(hidden.contains(AllEpisodesFragment.TAG)) Assert.assertTrue(hidden.contains(AllEpisodesFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)) Assert.assertTrue(hidden.contains(HistoryFragment.TAG))
} }
@Test @Test
fun testDrawerPreferencesUnhideSomeElements() { fun testDrawerPreferencesUnhideSomeElements() {
var hidden = listOf(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG) var hidden = listOf(HistoryFragment.TAG, DownloadsFragment.TAG)
hiddenDrawerItems = hidden hiddenDrawerItems = hidden
activityRule.launchActivity(Intent()) activityRule.launchActivity(Intent())
openNavDrawer() openNavDrawer()
@ -166,7 +166,7 @@ class NavigationDrawerTest {
hidden = hiddenDrawerItems?.filterNotNull()?: listOf() hidden = hiddenDrawerItems?.filterNotNull()?: listOf()
Assert.assertEquals(2, hidden.size.toLong()) Assert.assertEquals(2, hidden.size.toLong())
Assert.assertTrue(hidden.contains(QueueFragment.TAG)) Assert.assertTrue(hidden.contains(QueueFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG)) Assert.assertTrue(hidden.contains(HistoryFragment.TAG))
} }

View File

@ -17,6 +17,9 @@ import com.google.android.material.color.DynamicColors
import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.Iconify
import com.joanzapata.iconify.fonts.FontAwesomeModule import com.joanzapata.iconify.fonts.FontAwesomeModule
import com.joanzapata.iconify.fonts.MaterialModule import com.joanzapata.iconify.fonts.MaterialModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/** Main application class. */ /** Main application class. */
class PodciniApp : Application() { class PodciniApp : Application() {
@ -42,9 +45,12 @@ class PodciniApp : Application() {
singleton = this singleton = this
ClientConfigurator.initialize(this) runBlocking {
PreferenceUpgrader.checkUpgrades(this) withContext(Dispatchers.IO) {
ClientConfigurator.initialize(this@PodciniApp)
PreferenceUpgrader.checkUpgrades(this@PodciniApp)
}
}
Iconify.with(FontAwesomeModule()) Iconify.with(FontAwesomeModule())
Iconify.with(MaterialModule()) Iconify.with(MaterialModule())

View File

@ -7,6 +7,7 @@ import android.util.Log
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient
import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.Feed
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.content.SharedPreferences
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
@ -81,6 +82,11 @@ class ItunesTopListLoader(private val context: Context) {
const val COUNTRY_CODE_UNSET: String = "99" const val COUNTRY_CODE_UNSET: String = "99"
private const val NUM_LOADED = 25 private const val NUM_LOADED = 25
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
}
private fun removeSubscribed(suggestedPodcasts: List<PodcastSearchResult>, subscribedFeeds: List<Feed>, limit: Int): List<PodcastSearchResult> { private fun removeSubscribed(suggestedPodcasts: List<PodcastSearchResult>, subscribedFeeds: List<Feed>, limit: Int): List<PodcastSearchResult> {
val subscribedPodcastsSet: MutableSet<String> = HashSet() val subscribedPodcastsSet: MutableSet<String> = HashSet()
for (subscribedFeed in subscribedFeeds) { for (subscribedFeed in subscribedFeeds) {

View File

@ -29,6 +29,7 @@ import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import kotlinx.coroutines.runBlocking
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -115,7 +116,7 @@ class EpisodeDownloadWorker(context: Context, params: WorkerParameters) : Worker
if (dest.exists()) { if (dest.exists()) {
media.file_url = request.destination media.file_url = request.destination
try { try {
DBWriter.persistFeedMedia(media).get() runBlocking { DBWriter.persistFeedMedia(media).join() }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "ExecutionException in writeFileUrl: " + e.message) Log.e(TAG, "ExecutionException in writeFileUrl: " + e.message)
} }

View File

@ -16,6 +16,7 @@ import android.content.Context
import android.media.MediaMetadataRetriever import android.media.MediaMetadataRetriever
import android.util.Log import android.util.Log
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.io.File import java.io.File
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
@ -60,7 +61,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
val item = media.item val item = media.item
try { try {
DBWriter.persistFeedMedia(media).get() runBlocking { DBWriter.persistFeedMedia(media).join() }
// we've received the media, we don't want to autodownload it again // we've received the media, we don't want to autodownload it again
if (item != null) { if (item != null) {
@ -68,7 +69,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
// setFeedItem() signals (via EventBus) that the item has been updated, // setFeedItem() signals (via EventBus) that the item has been updated,
// so we do it after the enclosing media has been updated above, // so we do it after the enclosing media has been updated above,
// to ensure subscribers will get the updated FeedMedia as well // to ensure subscribers will get the updated FeedMedia as well
DBWriter.persistFeedItem(item).get() runBlocking { DBWriter.persistFeedItem(item).join() }
if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent()) if (broadcastUnreadStateUpdate) EventFlow.postEvent(FlowEvent.UnreadItemsUpdateEvent())
} }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {

View File

@ -46,6 +46,7 @@ class CompositeX509TrustManager(private val trustManagers: List<X509TrustManager
override fun getAcceptedIssuers(): Array<X509Certificate> { override fun getAcceptedIssuers(): Array<X509Certificate> {
val certificates: MutableList<X509Certificate> = ArrayList() val certificates: MutableList<X509Certificate> = ArrayList()
for (trustManager in trustManagers) { for (trustManager in trustManagers) {
// TODO: appears time consuming
certificates.addAll(listOf(*trustManager.acceptedIssuers)) certificates.addAll(listOf(*trustManager.acceptedIssuers))
} }
return certificates.toTypedArray<X509Certificate>() return certificates.toTypedArray<X509Certificate>()

View File

@ -57,6 +57,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
@UnstableApi override fun doWork(): Result { @UnstableApi override fun doWork(): Result {
Logd(TAG, "doWork() called") Logd(TAG, "doWork() called")
val activeSyncProvider = getActiveSyncProvider() ?: return Result.failure() val activeSyncProvider = getActiveSyncProvider() ?: return Result.failure()
Logd(TAG, "doWork() got syn provider")
SynchronizationSettings.updateLastSynchronizationAttempt() SynchronizationSettings.updateLastSynchronizationAttempt()
setCurrentlyActive(true) setCurrentlyActive(true)
@ -165,15 +166,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
return@launch return@launch
} }
scope.cancel() scope.cancel()
// try {
// while (true) {
// Thread.sleep(1000)
// val event = EventBus.getDefault().getStickyEvent(FlowEvent.FeedUpdateRunningEvent::class.java)
// if (event == null || !event.isFeedUpdateRunning) return
// }
// } catch (e: InterruptedException) {
// e.printStackTrace()
// }
} }
fun getEpisodeActions(syncServiceImpl: ISyncService) : Pair<Long, Long> { fun getEpisodeActions(syncServiceImpl: ISyncService) : Pair<Long, Long> {
@ -315,11 +307,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
if (selectedService == null) return null if (selectedService == null) return null
return when (selectedService) { return when (selectedService) {
// SynchronizationProviderViewData.WIFI -> {
//// if (hosturl != null) WifiImplSyncService(hosturl!!, hostport)
//// else null
// null
// }
SynchronizationProviderViewData.GPODDER_NET -> GpodnetService(getHttpClient(), hosturl, deviceID?:"", username?:"", password?:"") SynchronizationProviderViewData.GPODDER_NET -> GpodnetService(getHttpClient(), hosturl, deviceID?:"", username?:"", password?:"")
SynchronizationProviderViewData.NEXTCLOUD_GPODDER -> NextcloudSyncService(getHttpClient(), hosturl, username?:"", password?:"") SynchronizationProviderViewData.NEXTCLOUD_GPODDER -> NextcloudSyncService(getHttpClient(), hosturl, username?:"", password?:"")
} }

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.LocalBinder import ac.mdiq.podcini.playback.service.PlaybackService.LocalBinder
import ac.mdiq.podcini.playback.service.PlaybackServiceConstants import ac.mdiq.podcini.playback.service.PlaybackServiceConstants
import ac.mdiq.podcini.preferences.PlaybackPreferences import ac.mdiq.podcini.preferences.PlaybackPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
@ -24,8 +25,11 @@ import android.view.SurfaceHolder
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/** /**
* Communicates with the playback service. GUI classes should use this class to * Communicates with the playback service. GUI classes should use this class to
@ -73,6 +77,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
private fun procFlowEvents() { private fun procFlowEvents() {
activity.lifecycleScope.launch { activity.lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED) init() is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED) init()
else -> {} else -> {}
@ -305,9 +310,13 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
fun getMedia(): Playable? { fun getMedia(): Playable? {
if (media == null && playbackService != null) media = playbackService!!.mPlayerInfo.playable if (media == null && playbackService != null) media = playbackService!!.mPlayerInfo.playable
if (media == null) media = PlaybackPreferences.createInstanceFromPreferences(activity) if (media != null) return media
return runBlocking {
return media media = withContext(Dispatchers.IO) {
loadPlayableFromPreferences()
}
media
}
} }
fun seekTo(time: Int) { fun seekTo(time: Int) {

View File

@ -8,6 +8,7 @@ import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.RewindAfterPauseUtils import ac.mdiq.podcini.playback.base.RewindAfterPauseUtils
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBWriter.ioScope
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
@ -159,15 +160,15 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
private fun setDataSource(m: MediaMetadata, s: String, user: String?, password: String?) { private fun setDataSource(m: MediaMetadata, s: String, user: String?, password: String?) {
Logd(TAG, "setDataSource: $s") Logd(TAG, "setDataSource: $s")
val httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory) if (httpDataSourceFactory == null)
.setUserAgent(ClientConfig.USER_AGENT) httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory).setUserAgent(ClientConfig.USER_AGENT)
if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) { if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) {
val requestProperties = HashMap<String, String>() val requestProperties = HashMap<String, String>()
requestProperties["Authorization"] = HttpCredentialEncoder.encode(user, password, "ISO-8859-1") requestProperties["Authorization"] = HttpCredentialEncoder.encode(user, password, "ISO-8859-1")
httpDataSourceFactory.setDefaultRequestProperties(requestProperties) httpDataSourceFactory!!.setDefaultRequestProperties(requestProperties)
} }
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context, null, httpDataSourceFactory) val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(context, null, httpDataSourceFactory!!)
val extractorsFactory = DefaultExtractorsFactory() val extractorsFactory = DefaultExtractorsFactory()
extractorsFactory.setConstantBitrateSeekingEnabled(true) extractorsFactory.setConstantBitrateSeekingEnabled(true)
extractorsFactory.setMp3ExtractorFlags(Mp3Extractor.FLAG_DISABLE_ID3_METADATA) extractorsFactory.setMp3ExtractorFlags(Mp3Extractor.FLAG_DISABLE_ID3_METADATA)
@ -282,6 +283,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
} }
else -> { else -> {
val localMediaurl = this.playable!!.getLocalMediaUrl() val localMediaurl = this.playable!!.getLocalMediaUrl()
// TODO: File(localMediaurl).canRead() time consuming
if (!localMediaurl.isNullOrEmpty() && File(localMediaurl).canRead()) setDataSource(metadata, localMediaurl, null, null) if (!localMediaurl.isNullOrEmpty() && File(localMediaurl).canRead()) setDataSource(metadata, localMediaurl, null, null)
else throw IOException("Unable to read local file $localMediaurl") else throw IOException("Unable to read local file $localMediaurl")
} }
@ -603,8 +605,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
* invalid values. * invalid values.
*/ */
override fun getVideoSize(): Pair<Int, Int>? { override fun getVideoSize(): Pair<Int, Int>? {
if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO) if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO) videoSize = Pair(videoWidth, videoHeight)
videoSize = Pair(videoWidth, videoHeight)
return videoSize return videoSize
} }
@ -664,6 +665,12 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
init { init {
mediaType = MediaType.UNKNOWN mediaType = MediaType.UNKNOWN
if (httpDataSourceFactory == null) {
ioScope.launch {
httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
.setUserAgent(ClientConfig.USER_AGENT)
}
}
if (exoPlayer == null) { if (exoPlayer == null) {
setupPlayerListener() setupPlayerListener()
createStaticPlayer(context) createStaticPlayer(context)
@ -837,6 +844,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
const val BUFFERING_ENDED: Int = -2 const val BUFFERING_ENDED: Int = -2
const val ERROR_CODE_OFFSET: Int = 1000 const val ERROR_CODE_OFFSET: Int = 1000
private var httpDataSourceFactory: OkHttpDataSource.Factory? = null
private var trackSelector: DefaultTrackSelector? = null private var trackSelector: DefaultTrackSelector? = null
var exoPlayer: ExoPlayer? = null var exoPlayer: ExoPlayer? = null
@ -897,6 +906,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
audioErrorListener = null audioErrorListener = null
bufferingUpdateListener = null bufferingUpdateListener = null
loudnessEnhancer = null loudnessEnhancer = null
httpDataSourceFactory = null
} }
} }
} }

View File

@ -11,7 +11,7 @@ import ac.mdiq.podcini.playback.cast.CastPsmp
import ac.mdiq.podcini.playback.cast.CastStateListener import ac.mdiq.podcini.playback.cast.CastStateListener
import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback import ac.mdiq.podcini.playback.service.PlaybackServiceTaskManager.PSTMCallback
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentEpisodeIsVideo
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed
@ -612,7 +612,7 @@ class PlaybackService : MediaSessionService() {
scope.launch { scope.launch {
try { try {
val playable = withContext(Dispatchers.IO) { val playable = withContext(Dispatchers.IO) {
createInstanceFromPreferences(applicationContext) loadPlayableFromPreferences()
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
startPlaying(playable, false) startPlaying(playable, false)
@ -783,6 +783,7 @@ class PlaybackService : MediaSessionService() {
private fun procFlowEvents() { private fun procFlowEvents() {
scope.launch { scope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlayerErrorEvent -> playerError(event) is FlowEvent.PlayerErrorEvent -> playerError(event)
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event) is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)

View File

@ -1,6 +1,7 @@
package ac.mdiq.podcini.preferences package ac.mdiq.podcini.preferences
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
@ -14,7 +15,6 @@ import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager
/** /**
* Provides access to preferences set by the playback service. A private * Provides access to preferences set by the playback service. A private
@ -88,43 +88,43 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
const val PLAYER_STATUS_OTHER: Int = 3 const val PLAYER_STATUS_OTHER: Int = 3
private var instance: PlaybackPreferences? = null private var instance: PlaybackPreferences? = null
private lateinit var prefs: SharedPreferences // private lateinit var prefs: SharedPreferences
@JvmStatic @JvmStatic
fun init(context: Context) { fun init(context: Context) {
instance = PlaybackPreferences() instance = PlaybackPreferences()
prefs = PreferenceManager.getDefaultSharedPreferences(context) // prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.registerOnSharedPreferenceChangeListener(instance) appPrefs.registerOnSharedPreferenceChangeListener(instance)
} }
@JvmStatic @JvmStatic
val currentlyPlayingMediaType: Long val currentlyPlayingMediaType: Long
get() = prefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING) get() = appPrefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING)
@JvmStatic @JvmStatic
val currentlyPlayingFeedMediaId: Long val currentlyPlayingFeedMediaId: Long
get() = prefs.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING) get() = appPrefs.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
@JvmStatic @JvmStatic
val currentEpisodeIsVideo: Boolean val currentEpisodeIsVideo: Boolean
get() = prefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false) get() = appPrefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false)
@JvmStatic @JvmStatic
val currentPlayerStatus: Int val currentPlayerStatus: Int
get() = prefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER) get() = appPrefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER)
@JvmStatic @JvmStatic
var currentlyPlayingTemporaryPlaybackSpeed: Float var currentlyPlayingTemporaryPlaybackSpeed: Float
get() = prefs.getFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, FeedPreferences.SPEED_USE_GLOBAL) get() = appPrefs.getFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, FeedPreferences.SPEED_USE_GLOBAL)
set(speed) { set(speed) {
val editor = prefs.edit() val editor = appPrefs.edit()
editor.putFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, speed) editor.putFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, speed)
editor.apply() editor.apply()
} }
@JvmStatic @JvmStatic
fun writeNoMediaPlaying() { fun writeNoMediaPlaying() {
val editor = prefs.edit() val editor = appPrefs.edit()
editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING) editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING)
editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING) editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING)
editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING) editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
@ -135,7 +135,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
@JvmStatic @JvmStatic
fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus, item: FeedItem? = null) { fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus, item: FeedItem? = null) {
Logd(TAG, "Writing playback preferences ${playable?.getIdentifier()}") Logd(TAG, "Writing playback preferences ${playable?.getIdentifier()}")
val editor = prefs.edit() val editor = appPrefs.edit()
if (playable == null) { if (playable == null) {
writeNoMediaPlaying() writeNoMediaPlaying()
@ -162,14 +162,14 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
fun writePlayerStatus(playerStatus: PlayerStatus) { fun writePlayerStatus(playerStatus: PlayerStatus) {
Logd(TAG, "Writing player status playback preferences") Logd(TAG, "Writing player status playback preferences")
val editor = prefs.edit() val editor = appPrefs.edit()
editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus)) editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus))
editor.apply() editor.apply()
} }
@JvmStatic @JvmStatic
fun clearCurrentlyPlayingTemporaryPlaybackSpeed() { fun clearCurrentlyPlayingTemporaryPlaybackSpeed() {
val editor = prefs.edit() val editor = appPrefs.edit()
editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED) editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED)
editor.apply() editor.apply()
} }
@ -190,40 +190,23 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
* @return The restored Playable object * @return The restored Playable object
*/ */
@JvmStatic @JvmStatic
fun createInstanceFromPreferences(context: Context): Playable? { fun loadPlayableFromPreferences(): Playable? {
val currentlyPlayingMedia = currentlyPlayingMediaType val currentlyPlayingType = currentlyPlayingMediaType
Logd(TAG, "currentlyPlayingMedia: $currentlyPlayingMedia") Logd(TAG, "loadPlayableFromPreferences currentlyPlayingType: $currentlyPlayingType")
if (currentlyPlayingMedia != NO_MEDIA_PLAYING) { if (currentlyPlayingType != NO_MEDIA_PLAYING) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) val type = currentlyPlayingType.toInt()
return createInstanceFromPreferences(currentlyPlayingMedia.toInt(), prefs) if (type == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
var result: Playable? = null
val mediaId = appPrefs.getLong(FeedMedia.PREF_MEDIA_ID, -1)
if (mediaId != -1L) result = DBReader.getFeedMedia(mediaId)
Logd(TAG, "playable loaded: ${result?.getIdentifier()}")
return result
} else {
Log.e(TAG, "Could not restore Playable object from preferences")
return null
}
} }
return null return null
} }
/**
* Restores a playable object from a sharedPreferences file. This method might load data from the database,
* depending on the type of playable that was restored.
*
* @param type An integer that represents the type of the Playable object
* that is restored.
* @param pref The SharedPreferences file from which the Playable object
* is restored
* @return The restored Playable object
*/
private fun createInstanceFromPreferences(type: Int, pref: SharedPreferences): Playable? {
if (type == FeedMedia.PLAYABLE_TYPE_FEEDMEDIA) {
return createFeedMediaInstance(pref)
} else {
Log.e(TAG, "Could not restore Playable object from preferences")
return null
}
}
private fun createFeedMediaInstance(pref: SharedPreferences): Playable? {
var result: Playable? = null
val mediaId = pref.getLong(FeedMedia.PREF_MEDIA_ID, -1)
if (mediaId != -1L) result = DBReader.getFeedMedia(mediaId)
return result
}
} }
} }

View File

@ -16,6 +16,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.preferences.UserPreferences.theme import ac.mdiq.podcini.preferences.UserPreferences.theme
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.getSharedPrefs
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.prefs
import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment
import ac.mdiq.podcini.ui.fragment.QueueFragment import ac.mdiq.podcini.ui.fragment.QueueFragment
import ac.mdiq.podcini.util.error.CrashReportWriter.Companion.file import ac.mdiq.podcini.util.error.CrashReportWriter.Companion.file
@ -96,8 +98,9 @@ object PreferenceUpgrader {
prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON, KeyEvent.KEYCODE_MEDIA_PREVIOUS.toString()).apply() prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON, KeyEvent.KEYCODE_MEDIA_PREVIOUS.toString()).apply()
} }
if (oldVersion < 2040000) { if (oldVersion < 2040000) {
val swipePrefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE) getSharedPrefs(context)
swipePrefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG, // val swipePrefs = context.getSharedPreferences(SwipeActions.SWIPE_ACTIONS_PREF_NAME, Context.MODE_PRIVATE)
SwipeActions.prefs!!.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG,
SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply() SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply()
} }
if (oldVersion < 2050000) prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply() if (oldVersion < 2050000) prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply()

View File

@ -138,7 +138,7 @@ object UserPreferences {
const val DEFAULT_PAGE_REMEMBER: String = "remember" const val DEFAULT_PAGE_REMEMBER: String = "remember"
private lateinit var context: Context private lateinit var context: Context
private lateinit var prefs: SharedPreferences lateinit var appPrefs: SharedPreferences
/** /**
* Sets up the UserPreferences class. * Sets up the UserPreferences class.
@ -150,41 +150,41 @@ object UserPreferences {
Logd(TAG, "Creating new instance of UserPreferences") Logd(TAG, "Creating new instance of UserPreferences")
UserPreferences.context = context.applicationContext UserPreferences.context = context.applicationContext
prefs = PreferenceManager.getDefaultSharedPreferences(context) appPrefs = PreferenceManager.getDefaultSharedPreferences(context)
createNoMediaFile() createNoMediaFile()
} }
@JvmStatic @JvmStatic
var theme: ThemePreference var theme: ThemePreference
get() = when (prefs.getString(PREF_THEME, "system")) { get() = when (appPrefs.getString(PREF_THEME, "system")) {
"0" -> ThemePreference.LIGHT "0" -> ThemePreference.LIGHT
"1" -> ThemePreference.DARK "1" -> ThemePreference.DARK
else -> ThemePreference.SYSTEM else -> ThemePreference.SYSTEM
} }
set(theme) { set(theme) {
when (theme) { when (theme) {
ThemePreference.LIGHT -> prefs.edit().putString(PREF_THEME, "0").apply() ThemePreference.LIGHT -> appPrefs.edit().putString(PREF_THEME, "0").apply()
ThemePreference.DARK -> prefs.edit().putString(PREF_THEME, "1").apply() ThemePreference.DARK -> appPrefs.edit().putString(PREF_THEME, "1").apply()
else -> prefs.edit().putString(PREF_THEME, "system").apply() else -> appPrefs.edit().putString(PREF_THEME, "system").apply()
} }
} }
val isBlackTheme: Boolean val isBlackTheme: Boolean
get() = prefs.getBoolean(PREF_THEME_BLACK, false) get() = appPrefs.getBoolean(PREF_THEME_BLACK, false)
val isThemeColorTinted: Boolean val isThemeColorTinted: Boolean
get() = Build.VERSION.SDK_INT >= 31 && prefs.getBoolean(PREF_TINTED_COLORS, false) get() = Build.VERSION.SDK_INT >= 31 && appPrefs.getBoolean(PREF_TINTED_COLORS, false)
@JvmStatic @JvmStatic
var hiddenDrawerItems: List<String> var hiddenDrawerItems: List<String>
get() { get() {
val hiddenItems = prefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "") val hiddenItems = appPrefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "")
return ArrayList(listOf(*TextUtils.split(hiddenItems, ","))) return ArrayList(listOf(*TextUtils.split(hiddenItems, ",")))
} }
set(items) { set(items) {
val str = TextUtils.join(",", items) val str = TextUtils.join(",", items)
prefs.edit() appPrefs.edit()
.putString(PREF_HIDDEN_DRAWER_ITEMS, str) .putString(PREF_HIDDEN_DRAWER_ITEMS, str)
.apply() .apply()
} }
@ -192,7 +192,7 @@ object UserPreferences {
@JvmStatic @JvmStatic
var fullNotificationButtons: List<Int> var fullNotificationButtons: List<Int>
get() { get() {
val buttons = TextUtils.split(prefs.getString(PREF_FULL_NOTIFICATION_BUTTONS, "$NOTIFICATION_BUTTON_SKIP,$NOTIFICATION_BUTTON_PLAYBACK_SPEED"), ",") val buttons = TextUtils.split(appPrefs.getString(PREF_FULL_NOTIFICATION_BUTTONS, "$NOTIFICATION_BUTTON_SKIP,$NOTIFICATION_BUTTON_PLAYBACK_SPEED"), ",")
val notificationButtons: MutableList<Int> = ArrayList() val notificationButtons: MutableList<Int> = ArrayList()
for (button in buttons) { for (button in buttons) {
notificationButtons.add(button.toInt()) notificationButtons.add(button.toInt())
@ -200,8 +200,8 @@ object UserPreferences {
return notificationButtons return notificationButtons
} }
set(items) { set(items) {
val str = TextUtils.join(",", items!!) val str = TextUtils.join(",", items)
prefs.edit() appPrefs.edit()
.putString(PREF_FULL_NOTIFICATION_BUTTONS, str) .putString(PREF_FULL_NOTIFICATION_BUTTONS, str)
.apply() .apply()
} }
@ -216,7 +216,7 @@ object UserPreferences {
* @return `true` if button should be shown, `false` otherwise * @return `true` if button should be shown, `false` otherwise
*/ */
private fun showButtonOnFullNotification(buttonId: Int): Boolean { private fun showButtonOnFullNotification(buttonId: Int): Boolean {
return fullNotificationButtons!!.contains(buttonId) return fullNotificationButtons.contains(buttonId)
} }
@JvmStatic @JvmStatic
@ -237,13 +237,13 @@ object UserPreferences {
@JvmStatic @JvmStatic
val feedOrder: Int val feedOrder: Int
get() { get() {
val value = prefs.getString(PREF_DRAWER_FEED_ORDER, "" + FEED_ORDER_COUNTER) val value = appPrefs.getString(PREF_DRAWER_FEED_ORDER, "" + FEED_ORDER_COUNTER)
return value!!.toInt() return value!!.toInt()
} }
@JvmStatic @JvmStatic
fun setFeedOrder(selected: String?) { fun setFeedOrder(selected: String?) {
prefs.edit() appPrefs.edit()
.putString(PREF_DRAWER_FEED_ORDER, selected) .putString(PREF_DRAWER_FEED_ORDER, selected)
.apply() .apply()
} }
@ -251,7 +251,7 @@ object UserPreferences {
@JvmStatic @JvmStatic
val feedCounterSetting: FeedCounter val feedCounterSetting: FeedCounter
get() { get() {
val value = prefs.getString(PREF_DRAWER_FEED_COUNTER, "" + FeedCounter.SHOW_UNPLAYED.id) val value = appPrefs.getString(PREF_DRAWER_FEED_COUNTER, "" + FeedCounter.SHOW_UNPLAYED.id)
return FeedCounter.fromOrdinal(value!!.toInt()) return FeedCounter.fromOrdinal(value!!.toInt())
} }
@ -259,14 +259,14 @@ object UserPreferences {
/** /**
* @return `true` if episodes should use their own cover, `false` otherwise * @return `true` if episodes should use their own cover, `false` otherwise
*/ */
get() = prefs.getBoolean(PREF_USE_EPISODE_COVER, true) get() = appPrefs.getBoolean(PREF_USE_EPISODE_COVER, true)
/** /**
* @return `true` if we should show remaining time or the duration * @return `true` if we should show remaining time or the duration
*/ */
@JvmStatic @JvmStatic
fun shouldShowRemainingTime(): Boolean { fun shouldShowRemainingTime(): Boolean {
return prefs.getBoolean(PREF_SHOW_TIME_LEFT, false) return appPrefs.getBoolean(PREF_SHOW_TIME_LEFT, false)
} }
/** /**
@ -277,7 +277,7 @@ object UserPreferences {
*/ */
@JvmStatic @JvmStatic
fun setShowRemainTimeSetting(showRemain: Boolean?) { fun setShowRemainTimeSetting(showRemain: Boolean?) {
prefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showRemain!!).apply() appPrefs.edit().putBoolean(PREF_SHOW_TIME_LEFT, showRemain!!).apply()
} }
val notifyPriority: Int val notifyPriority: Int
@ -286,7 +286,7 @@ object UserPreferences {
* *
* @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT
*/ */
get() = if (prefs.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) NotificationCompat.PRIORITY_MAX else NotificationCompat.PRIORITY_DEFAULT get() = if (appPrefs.getBoolean(PREF_EXPANDED_NOTIFICATION, false)) NotificationCompat.PRIORITY_MAX else NotificationCompat.PRIORITY_DEFAULT
@JvmStatic @JvmStatic
val isPersistNotify: Boolean val isPersistNotify: Boolean
@ -295,23 +295,23 @@ object UserPreferences {
* *
* @return `true` if notifications are persistent, `false` otherwise * @return `true` if notifications are persistent, `false` otherwise
*/ */
get() = prefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true) get() = appPrefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true)
@JvmStatic @JvmStatic
val showDownloadReportRaw: Boolean val showDownloadReportRaw: Boolean
/** /**
* Used for migration of the preference to system notification channels. * Used for migration of the preference to system notification channels.
*/ */
get() = prefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true) get() = appPrefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true)
fun enqueueDownloadedEpisodes(): Boolean { fun enqueueDownloadedEpisodes(): Boolean {
return prefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true) return appPrefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true)
} }
@JvmStatic @JvmStatic
var enqueueLocation: EnqueueLocation var enqueueLocation: EnqueueLocation
get() { get() {
val valStr = prefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name) val valStr = appPrefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name)
try { try {
return EnqueueLocation.valueOf(valStr!!) return EnqueueLocation.valueOf(valStr!!)
} catch (t: Throwable) { } catch (t: Throwable) {
@ -321,64 +321,64 @@ object UserPreferences {
} }
} }
set(location) { set(location) {
prefs.edit().putString(PREF_ENQUEUE_LOCATION, location.name).apply() appPrefs.edit().putString(PREF_ENQUEUE_LOCATION, location.name).apply()
} }
@JvmStatic @JvmStatic
val isPauseOnHeadsetDisconnect: Boolean val isPauseOnHeadsetDisconnect: Boolean
get() = prefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true) get() = appPrefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true)
@JvmStatic @JvmStatic
val isUnpauseOnHeadsetReconnect: Boolean val isUnpauseOnHeadsetReconnect: Boolean
get() = prefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true) get() = appPrefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true)
@JvmStatic @JvmStatic
val isUnpauseOnBluetoothReconnect: Boolean val isUnpauseOnBluetoothReconnect: Boolean
get() = prefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false) get() = appPrefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false)
@JvmStatic @JvmStatic
val hardwareForwardButton: Int val hardwareForwardButton: Int
get() = prefs.getString(PREF_HARDWARE_FORWARD_BUTTON, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt() get() = appPrefs.getString(PREF_HARDWARE_FORWARD_BUTTON, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt()
@JvmStatic @JvmStatic
val hardwarePreviousButton: Int val hardwarePreviousButton: Int
get() = prefs.getString(PREF_HARDWARE_PREVIOUS_BUTTON, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt() get() = appPrefs.getString(PREF_HARDWARE_PREVIOUS_BUTTON, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt()
@JvmStatic @JvmStatic
@set:VisibleForTesting @set:VisibleForTesting
var isFollowQueue: Boolean var isFollowQueue: Boolean
get() = prefs.getBoolean(PREF_FOLLOW_QUEUE, true) get() = appPrefs.getBoolean(PREF_FOLLOW_QUEUE, true)
/** /**
* Set to true to enable Continuous Playback * Set to true to enable Continuous Playback
*/ */
set(value) { set(value) {
prefs.edit().putBoolean(PREF_FOLLOW_QUEUE, value).apply() appPrefs.edit().putBoolean(PREF_FOLLOW_QUEUE, value).apply()
} }
@JvmStatic @JvmStatic
fun shouldSkipKeepEpisode(): Boolean { fun shouldSkipKeepEpisode(): Boolean {
return prefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true) return appPrefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true)
} }
@JvmStatic @JvmStatic
fun shouldFavoriteKeepEpisode(): Boolean { fun shouldFavoriteKeepEpisode(): Boolean {
return prefs.getBoolean(PREF_FAVORITE_KEEPS_EPISODE, true) return appPrefs.getBoolean(PREF_FAVORITE_KEEPS_EPISODE, true)
} }
@JvmStatic @JvmStatic
val isAutoDelete: Boolean val isAutoDelete: Boolean
get() = prefs.getBoolean(PREF_AUTO_DELETE, false) get() = appPrefs.getBoolean(PREF_AUTO_DELETE, false)
@JvmStatic @JvmStatic
val isAutoDeleteLocal: Boolean val isAutoDeleteLocal: Boolean
get() = prefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false) get() = appPrefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false)
val smartMarkAsPlayedSecs: Int val smartMarkAsPlayedSecs: Int
get() = prefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")!!.toInt() get() = appPrefs.getString(PREF_SMART_MARK_AS_PLAYED_SECS, "30")!!.toInt()
@JvmStatic @JvmStatic
fun shouldDeleteRemoveFromQueue(): Boolean { fun shouldDeleteRemoveFromQueue(): Boolean {
return prefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false) return appPrefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false)
} }
@JvmStatic @JvmStatic
@ -389,7 +389,7 @@ object UserPreferences {
private val audioPlaybackSpeed: Float private val audioPlaybackSpeed: Float
get() { get() {
try { try {
return prefs.getString(PREF_PLAYBACK_SPEED, "1.00")!!.toFloat() return appPrefs.getString(PREF_PLAYBACK_SPEED, "1.00")!!.toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
setPlaybackSpeed(1.0f) setPlaybackSpeed(1.0f)
@ -400,7 +400,7 @@ object UserPreferences {
val videoPlayMode: Int val videoPlayMode: Int
get() { get() {
try { try {
return prefs.getString(PREF_VIDEO_MODE, "1")!!.toInt() return appPrefs.getString(PREF_VIDEO_MODE, "1")!!.toInt()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
setVideoMode(1) setVideoMode(1)
@ -412,7 +412,7 @@ object UserPreferences {
var videoPlaybackSpeed: Float var videoPlaybackSpeed: Float
get() { get() {
try { try {
return prefs.getString(PREF_VIDEO_PLAYBACK_SPEED, "1.00")!!.toFloat() return appPrefs.getString(PREF_VIDEO_PLAYBACK_SPEED, "1.00")!!.toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
videoPlaybackSpeed = 1.0f videoPlaybackSpeed = 1.0f
@ -420,21 +420,21 @@ object UserPreferences {
} }
} }
set(speed) { set(speed) {
prefs.edit() appPrefs.edit()
.putString(PREF_VIDEO_PLAYBACK_SPEED, speed.toString()) .putString(PREF_VIDEO_PLAYBACK_SPEED, speed.toString())
.apply() .apply()
} }
@JvmStatic @JvmStatic
var isSkipSilence: Boolean var isSkipSilence: Boolean
get() = prefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false) get() = appPrefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false)
set(skipSilence) { set(skipSilence) {
prefs.edit().putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence).apply() appPrefs.edit().putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence).apply()
} }
@JvmStatic @JvmStatic
var playbackSpeedArray: List<Float> var playbackSpeedArray: List<Float>
get() = readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null)) get() = readPlaybackSpeedArray(appPrefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null))
set(speeds) { set(speeds) {
val format = DecimalFormatSymbols(Locale.US) val format = DecimalFormatSymbols(Locale.US)
format.decimalSeparator = '.' format.decimalSeparator = '.'
@ -443,16 +443,16 @@ object UserPreferences {
for (speed in speeds) { for (speed in speeds) {
jsonArray.put(speedFormat.format(speed.toDouble())) jsonArray.put(speedFormat.format(speed.toDouble()))
} }
prefs.edit().putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()).apply() appPrefs.edit().putString(PREF_PLAYBACK_SPEED_ARRAY, jsonArray.toString()).apply()
} }
@JvmStatic @JvmStatic
fun shouldPauseForFocusLoss(): Boolean { fun shouldPauseForFocusLoss(): Boolean {
return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true) return appPrefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true)
} }
val updateInterval: Long val updateInterval: Long
get() = prefs.getString(PREF_UPDATE_INTERVAL, "12")!!.toInt().toLong() get() = appPrefs.getString(PREF_UPDATE_INTERVAL, "12")!!.toInt().toLong()
val isAutoUpdateDisabled: Boolean val isAutoUpdateDisabled: Boolean
get() = updateInterval == 0L get() = updateInterval == 0L
@ -460,7 +460,7 @@ object UserPreferences {
private fun isAllowMobileFor(type: String): Boolean { private fun isAllowMobileFor(type: String): Boolean {
val defaultValue = HashSet<String>() val defaultValue = HashSet<String>()
defaultValue.add("images") defaultValue.add("images")
val allowed = prefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue) val allowed = appPrefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue)
return allowed!!.contains(type) return allowed!!.contains(type)
} }
@ -509,12 +509,12 @@ object UserPreferences {
private fun setAllowMobileFor(type: String, allow: Boolean) { private fun setAllowMobileFor(type: String, allow: Boolean) {
val defaultValue = HashSet<String>() val defaultValue = HashSet<String>()
defaultValue.add("images") defaultValue.add("images")
val getValueStringSet = prefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue) val getValueStringSet = appPrefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue)
val allowed: MutableSet<String> = HashSet(getValueStringSet!!) val allowed: MutableSet<String> = HashSet(getValueStringSet!!)
if (allow) allowed.add(type) if (allow) allowed.add(type)
else allowed.remove(type) else allowed.remove(type)
prefs.edit().putStringSet(PREF_MOBILE_UPDATE, allowed).apply() appPrefs.edit().putStringSet(PREF_MOBILE_UPDATE, allowed).apply()
} }
@JvmStatic @JvmStatic
@ -524,29 +524,29 @@ object UserPreferences {
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to * negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
* 'unlimited'. * 'unlimited'.
*/ */
get() = prefs.getString(PREF_EPISODE_CACHE_SIZE, "20")!!.toInt() get() = appPrefs.getString(PREF_EPISODE_CACHE_SIZE, "20")!!.toInt()
@JvmStatic @JvmStatic
@set:VisibleForTesting @set:VisibleForTesting
var isEnableAutodownload: Boolean var isEnableAutodownload: Boolean
get() = prefs.getBoolean(PREF_ENABLE_AUTODL, false) get() = appPrefs.getBoolean(PREF_ENABLE_AUTODL, false)
set(enabled) { set(enabled) {
prefs.edit().putBoolean(PREF_ENABLE_AUTODL, enabled).apply() appPrefs.edit().putBoolean(PREF_ENABLE_AUTODL, enabled).apply()
} }
@JvmStatic @JvmStatic
val isEnableAutodownloadOnBattery: Boolean val isEnableAutodownloadOnBattery: Boolean
get() = prefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true) get() = appPrefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true)
@JvmStatic @JvmStatic
val isEnableAutodownloadWifiFilter: Boolean val isEnableAutodownloadWifiFilter: Boolean
get() = Build.VERSION.SDK_INT < 29 && prefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false) get() = Build.VERSION.SDK_INT < 29 && appPrefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false)
@JvmStatic @JvmStatic
var speedforwardSpeed: Float var speedforwardSpeed: Float
get() { get() {
try { try {
return prefs.getString(PREF_SPEEDFORWRD_SPEED, "0.00")!!.toFloat() return appPrefs.getString(PREF_SPEEDFORWRD_SPEED, "0.00")!!.toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
speedforwardSpeed = 0.0f speedforwardSpeed = 0.0f
@ -554,14 +554,14 @@ object UserPreferences {
} }
} }
set(speed) { set(speed) {
prefs.edit().putString(PREF_SPEEDFORWRD_SPEED, speed.toString()).apply() appPrefs.edit().putString(PREF_SPEEDFORWRD_SPEED, speed.toString()).apply()
} }
@JvmStatic @JvmStatic
var fallbackSpeed: Float var fallbackSpeed: Float
get() { get() {
try { try {
return prefs.getString(PREF_FALLBACK_SPEED, "0.00")!!.toFloat() return appPrefs.getString(PREF_FALLBACK_SPEED, "0.00")!!.toFloat()
} catch (e: NumberFormatException) { } catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))
fallbackSpeed = 0.0f fallbackSpeed = 0.0f
@ -569,42 +569,42 @@ object UserPreferences {
} }
} }
set(speed) { set(speed) {
prefs.edit().putString(PREF_FALLBACK_SPEED, speed.toString()).apply() appPrefs.edit().putString(PREF_FALLBACK_SPEED, speed.toString()).apply()
} }
@JvmStatic @JvmStatic
var fastForwardSecs: Int var fastForwardSecs: Int
get() = prefs.getInt(PREF_FAST_FORWARD_SECS, 30) get() = appPrefs.getInt(PREF_FAST_FORWARD_SECS, 30)
set(secs) { set(secs) {
prefs.edit().putInt(PREF_FAST_FORWARD_SECS, secs).apply() appPrefs.edit().putInt(PREF_FAST_FORWARD_SECS, secs).apply()
} }
@JvmStatic @JvmStatic
var rewindSecs: Int var rewindSecs: Int
get() = prefs.getInt(PREF_REWIND_SECS, 10) get() = appPrefs.getInt(PREF_REWIND_SECS, 10)
set(secs) { set(secs) {
prefs.edit().putInt(PREF_REWIND_SECS, secs).apply() appPrefs.edit().putInt(PREF_REWIND_SECS, secs).apply()
} }
@JvmStatic @JvmStatic
val autodownloadSelectedNetworks: Array<String> val autodownloadSelectedNetworks: Array<String>
get() { get() {
val selectedNetWorks = prefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "") val selectedNetWorks = appPrefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "")
return TextUtils.split(selectedNetWorks, ",") return TextUtils.split(selectedNetWorks, ",")
} }
@JvmStatic @JvmStatic
var proxyConfig: ProxyConfig var proxyConfig: ProxyConfig
get() { get() {
val type = Proxy.Type.valueOf(prefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name)!!) val type = Proxy.Type.valueOf(appPrefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name)!!)
val host = prefs.getString(PREF_PROXY_HOST, null) val host = appPrefs.getString(PREF_PROXY_HOST, null)
val port = prefs.getInt(PREF_PROXY_PORT, 0) val port = appPrefs.getInt(PREF_PROXY_PORT, 0)
val username = prefs.getString(PREF_PROXY_USER, null) val username = appPrefs.getString(PREF_PROXY_USER, null)
val password = prefs.getString(PREF_PROXY_PASSWORD, null) val password = appPrefs.getString(PREF_PROXY_PASSWORD, null)
return ProxyConfig(type, host, port, username, password) return ProxyConfig(type, host, port, username, password)
} }
set(config) { set(config) {
val editor = prefs.edit() val editor = appPrefs.edit()
editor.putString(PREF_PROXY_TYPE, config.type.name) editor.putString(PREF_PROXY_TYPE, config.type.name)
if (config.host.isNullOrEmpty()) editor.remove(PREF_PROXY_HOST) if (config.host.isNullOrEmpty()) editor.remove(PREF_PROXY_HOST)
else editor.putString(PREF_PROXY_HOST, config.host) else editor.putString(PREF_PROXY_HOST, config.host)
@ -623,31 +623,31 @@ object UserPreferences {
@JvmStatic @JvmStatic
var isQueueLocked: Boolean var isQueueLocked: Boolean
get() = prefs.getBoolean(PREF_QUEUE_LOCKED, false) get() = appPrefs.getBoolean(PREF_QUEUE_LOCKED, false)
set(locked) { set(locked) {
prefs.edit().putBoolean(PREF_QUEUE_LOCKED, locked).apply() appPrefs.edit().putBoolean(PREF_QUEUE_LOCKED, locked).apply()
} }
@JvmStatic @JvmStatic
fun setPlaybackSpeed(speed: Float) { fun setPlaybackSpeed(speed: Float) {
prefs.edit().putString(PREF_PLAYBACK_SPEED, speed.toString()).apply() appPrefs.edit().putString(PREF_PLAYBACK_SPEED, speed.toString()).apply()
} }
@JvmStatic @JvmStatic
fun setVideoMode(mode: Int) { fun setVideoMode(mode: Int) {
prefs.edit().putString(PREF_VIDEO_MODE, mode.toString()).apply() appPrefs.edit().putString(PREF_VIDEO_MODE, mode.toString()).apply()
} }
@JvmStatic @JvmStatic
fun setAutodownloadSelectedNetworks(value: Array<String?>?) { fun setAutodownloadSelectedNetworks(value: Array<String?>?) {
prefs.edit().putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value!!)).apply() appPrefs.edit().putString(PREF_AUTODL_SELECTED_NETWORKS, TextUtils.join(",", value!!)).apply()
} }
@JvmStatic @JvmStatic
fun gpodnetNotificationsEnabled(): Boolean { fun gpodnetNotificationsEnabled(): Boolean {
if (Build.VERSION.SDK_INT >= 26) return true // System handles notification preferences if (Build.VERSION.SDK_INT >= 26) return true // System handles notification preferences
return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true) return appPrefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true)
} }
@JvmStatic @JvmStatic
@ -655,11 +655,11 @@ object UserPreferences {
/** /**
* Used for migration of the preference to system notification channels. * Used for migration of the preference to system notification channels.
*/ */
get() = prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true) get() = appPrefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true)
@JvmStatic @JvmStatic
fun setGpodnetNotificationsEnabled() { fun setGpodnetNotificationsEnabled() {
prefs.edit().putBoolean(PREF_GPODNET_NOTIFICATIONS, true).apply() appPrefs.edit().putBoolean(PREF_GPODNET_NOTIFICATIONS, true).apply()
} }
private fun readPlaybackSpeedArray(valueFromPrefs: String?): List<Float> { private fun readPlaybackSpeedArray(valueFromPrefs: String?): List<Float> {
@ -682,9 +682,9 @@ object UserPreferences {
@JvmStatic @JvmStatic
var episodeCleanupValue: Int var episodeCleanupValue: Int
get() = prefs.getString(PREF_EPISODE_CLEANUP, "" + EPISODE_CLEANUP_NULL)!!.toInt() get() = appPrefs.getString(PREF_EPISODE_CLEANUP, "" + EPISODE_CLEANUP_NULL)!!.toInt()
set(episodeCleanupValue) { set(episodeCleanupValue) {
prefs.edit().putString(PREF_EPISODE_CLEANUP, episodeCleanupValue.toString()).apply() appPrefs.edit().putString(PREF_EPISODE_CLEANUP, episodeCleanupValue.toString()).apply()
} }
/** /**
@ -697,7 +697,7 @@ object UserPreferences {
*/ */
@JvmStatic @JvmStatic
fun getDataFolder(type: String?): File? { fun getDataFolder(type: String?): File? {
var dataFolder = getTypeDir(prefs.getString(PREF_DATA_FOLDER, null), type) var dataFolder = getTypeDir(appPrefs.getString(PREF_DATA_FOLDER, null), type)
if (dataFolder == null || !dataFolder.canWrite()) { if (dataFolder == null || !dataFolder.canWrite()) {
Logd(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) dataFolder = context.getExternalFilesDir(type)
@ -730,7 +730,7 @@ object UserPreferences {
@JvmStatic @JvmStatic
fun setDataFolder(dir: String) { fun setDataFolder(dir: String) {
Logd(TAG, "setDataFolder(dir: $dir)") Logd(TAG, "setDataFolder(dir: $dir)")
prefs.edit().putString(PREF_DATA_FOLDER, dir).apply() appPrefs.edit().putString(PREF_DATA_FOLDER, dir).apply()
} }
/** /**
@ -751,26 +751,26 @@ object UserPreferences {
@JvmStatic @JvmStatic
var defaultPage: String? var defaultPage: String?
get() = prefs.getString(PREF_DEFAULT_PAGE, "SubscriptionFragment") get() = appPrefs.getString(PREF_DEFAULT_PAGE, "SubscriptionFragment")
set(defaultPage) { set(defaultPage) {
prefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply() appPrefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply()
} }
@JvmStatic @JvmStatic
fun backButtonOpensDrawer(): Boolean { fun backButtonOpensDrawer(): Boolean {
return prefs.getBoolean(PREF_BACK_OPENS_DRAWER, false) return appPrefs.getBoolean(PREF_BACK_OPENS_DRAWER, false)
} }
@JvmStatic @JvmStatic
fun timeRespectsSpeed(): Boolean { fun timeRespectsSpeed(): Boolean {
return prefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false) return appPrefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false)
} }
@JvmStatic @JvmStatic
var isStreamOverDownload: Boolean var isStreamOverDownload: Boolean
get() = prefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false) get() = appPrefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false)
set(stream) { set(stream) {
prefs.edit().putBoolean(PREF_STREAM_OVER_DOWNLOAD, stream).apply() appPrefs.edit().putBoolean(PREF_STREAM_OVER_DOWNLOAD, stream).apply()
} }
@JvmStatic @JvmStatic
@ -780,14 +780,14 @@ object UserPreferences {
* *
* @see .getQueueKeepSortedOrder * @see .getQueueKeepSortedOrder
*/ */
get() = prefs.getBoolean(PREF_QUEUE_KEEP_SORTED, false) get() = appPrefs.getBoolean(PREF_QUEUE_KEEP_SORTED, false)
/** /**
* Enables/disables the keep sorted mode of the queue. * Enables/disables the keep sorted mode of the queue.
* *
* @see .setQueueKeepSortedOrder * @see .setQueueKeepSortedOrder
*/ */
set(keepSorted) { set(keepSorted) {
prefs.edit().putBoolean(PREF_QUEUE_KEEP_SORTED, keepSorted).apply() appPrefs.edit().putBoolean(PREF_QUEUE_KEEP_SORTED, keepSorted).apply()
} }
@JvmStatic @JvmStatic
@ -799,7 +799,7 @@ object UserPreferences {
* @see .isQueueKeepSorted * @see .isQueueKeepSorted
*/ */
get() { get() {
val sortOrderStr = prefs.getString(PREF_QUEUE_KEEP_SORTED_ORDER, "use-default") val sortOrderStr = appPrefs.getString(PREF_QUEUE_KEEP_SORTED_ORDER, "use-default")
return SortOrder.parseWithDefault(sortOrderStr, SortOrder.DATE_NEW_OLD) return SortOrder.parseWithDefault(sortOrderStr, SortOrder.DATE_NEW_OLD)
} }
/** /**
@ -809,13 +809,13 @@ object UserPreferences {
*/ */
set(sortOrder) { set(sortOrder) {
if (sortOrder == null) return if (sortOrder == null) return
prefs.edit().putString(PREF_QUEUE_KEEP_SORTED_ORDER, sortOrder.name).apply() appPrefs.edit().putString(PREF_QUEUE_KEEP_SORTED_ORDER, sortOrder.name).apply()
} }
@JvmStatic @JvmStatic
val newEpisodesAction: NewEpisodesAction val newEpisodesAction: NewEpisodesAction
get() { get() {
val str = prefs.getString(PREF_NEW_EPISODES_ACTION, "" + NewEpisodesAction.GLOBAL.code) val str = appPrefs.getString(PREF_NEW_EPISODES_ACTION, "" + NewEpisodesAction.GLOBAL.code)
return NewEpisodesAction.fromCode(str!!.toInt()) return NewEpisodesAction.fromCode(str!!.toInt())
} }
@ -825,14 +825,14 @@ object UserPreferences {
* Returns the sort order for the downloads. * Returns the sort order for the downloads.
*/ */
get() { get() {
val sortOrderStr = prefs.getString(PREF_DOWNLOADS_SORTED_ORDER, "" + SortOrder.DATE_NEW_OLD.code) val sortOrderStr = appPrefs.getString(PREF_DOWNLOADS_SORTED_ORDER, "" + SortOrder.DATE_NEW_OLD.code)
return SortOrder.fromCodeString(sortOrderStr) return SortOrder.fromCodeString(sortOrderStr)
} }
/** /**
* Sets the sort order for the downloads. * Sets the sort order for the downloads.
*/ */
set(sortOrder) { set(sortOrder) {
prefs.edit().putString(PREF_DOWNLOADS_SORTED_ORDER, "" + sortOrder!!.code).apply() appPrefs.edit().putString(PREF_DOWNLOADS_SORTED_ORDER, "" + sortOrder!!.code).apply()
} }
// @JvmStatic // @JvmStatic
@ -855,11 +855,11 @@ object UserPreferences {
@JvmStatic @JvmStatic
var subscriptionsFilter: SubscriptionsFilter var subscriptionsFilter: SubscriptionsFilter
get() { get() {
val value = prefs.getString(PREF_FILTER_FEED, "") val value = appPrefs.getString(PREF_FILTER_FEED, "")
return SubscriptionsFilter(value) return SubscriptionsFilter(value)
} }
set(value) { set(value) {
prefs.edit().putString(PREF_FILTER_FEED, value.serialize()).apply() appPrefs.edit().putString(PREF_FILTER_FEED, value.serialize()).apply()
} }
@JvmStatic @JvmStatic
@ -870,16 +870,16 @@ object UserPreferences {
@JvmStatic @JvmStatic
var allEpisodesSortOrder: SortOrder? var allEpisodesSortOrder: SortOrder?
get() = SortOrder.fromCodeString(prefs.getString(PREF_SORT_ALL_EPISODES, "" + SortOrder.DATE_NEW_OLD.code)) get() = SortOrder.fromCodeString(appPrefs.getString(PREF_SORT_ALL_EPISODES, "" + SortOrder.DATE_NEW_OLD.code))
set(s) { set(s) {
prefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s!!.code).apply() appPrefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s!!.code).apply()
} }
@JvmStatic @JvmStatic
var prefFilterAllEpisodes: String var prefFilterAllEpisodes: String
get() = prefs.getString(PREF_FILTER_ALL_EPISODES, "")?:"" get() = appPrefs.getString(PREF_FILTER_ALL_EPISODES, "")?:""
set(filter) { set(filter) {
prefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply() appPrefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply()
} }
enum class ThemePreference { enum class ThemePreference {

View File

@ -15,6 +15,7 @@ import ac.mdiq.podcini.net.download.FeedUpdateManager.restartUpdateAlarm
import ac.mdiq.podcini.ui.dialog.ChooseDataFolderDialog import ac.mdiq.podcini.ui.dialog.ChooseDataFolderDialog
import ac.mdiq.podcini.ui.dialog.ProxyDialog import ac.mdiq.podcini.ui.dialog.ProxyDialog
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.preferences.UserPreferences.getDataFolder import ac.mdiq.podcini.preferences.UserPreferences.getDataFolder
import ac.mdiq.podcini.preferences.UserPreferences.setDataFolder import ac.mdiq.podcini.preferences.UserPreferences.setDataFolder
import kotlin.Any import kotlin.Any
@ -32,12 +33,12 @@ class DownloadsPreferencesFragment : PreferenceFragmentCompat(), OnSharedPrefere
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.downloads_pref) (activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.downloads_pref)
PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener(this) appPrefs.registerOnSharedPreferenceChangeListener(this)
} }
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
PreferenceManager.getDefaultSharedPreferences(requireContext()).unregisterOnSharedPreferenceChangeListener(this) appPrefs.unregisterOnSharedPreferenceChangeListener(this)
} }
override fun onResume() { override fun onResume() {

View File

@ -21,6 +21,7 @@ class MainPreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
Logd("MainPreferencesFragment", "onCreatePreferences") Logd("MainPreferencesFragment", "onCreatePreferences")
// TODO: this can be expensive
addPreferencesFromResource(R.xml.preferences) addPreferencesFromResource(R.xml.preferences)
setupMainScreen() setupMainScreen()
setupSearch() setupSearch()

View File

@ -37,7 +37,7 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() {
true true
} }
findPreference<Preference>(PREF_SWIPE_HISTORY)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { findPreference<Preference>(PREF_SWIPE_HISTORY)?.onPreferenceClickListener = Preference.OnPreferenceClickListener {
SwipeActionsDialog(requireContext(), PlaybackHistoryFragment.TAG).show (object : SwipeActionsDialog.Callback { SwipeActionsDialog(requireContext(), HistoryFragment.TAG).show (object : SwipeActionsDialog.Callback {
override fun onCall() {} override fun onCall() {}
}) })
true true

View File

@ -10,6 +10,9 @@ import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.InternalPlayerFragment
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.InternalPlayerFragment.Companion
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.app.Activity import android.app.Activity
@ -56,6 +59,7 @@ class SynchronizationPreferencesFragment : PreferenceFragmentCompat() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd("SynchronizationPreferencesFragment", "Received event: ${event}")
when (event) { when (event) {
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event) is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
else -> {} else -> {}

View File

@ -6,6 +6,8 @@ import ac.mdiq.podcini.net.sync.SynchronizationCredentials
import ac.mdiq.podcini.net.sync.SynchronizationSettings.setWifiSyncEnabled 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.hostPort
import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.startInstantSync import ac.mdiq.podcini.net.sync.wifi.WifiSyncService.Companion.startInstantSync
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
@ -96,6 +98,7 @@ import java.util.*
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event) is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
else -> {} else -> {}

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.receiver package ac.mdiq.podcini.receiver
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.SWIPE_ACTIONS_PREF_NAME
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider import android.appwidget.AppWidgetProvider
import android.content.ComponentName import android.content.ComponentName
@ -10,6 +11,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.content.SharedPreferences
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class PlayerWidget : AppWidgetProvider() { class PlayerWidget : AppWidgetProvider() {
@ -25,10 +27,9 @@ class PlayerWidget : AppWidgetProvider() {
Logd(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) WidgetUpdaterWorker.enqueueWork(context)
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) if (!prefs!!.getBoolean(KEY_WORKAROUND_ENABLED, false)) {
if (!prefs.getBoolean(KEY_WORKAROUND_ENABLED, false)) {
scheduleWorkaround(context) scheduleWorkaround(context)
prefs.edit().putBoolean(KEY_WORKAROUND_ENABLED, true).apply() prefs!!.edit().putBoolean(KEY_WORKAROUND_ENABLED, true).apply()
} }
} }
@ -40,26 +41,24 @@ class PlayerWidget : AppWidgetProvider() {
override fun onDeleted(context: Context, appWidgetIds: IntArray) { override fun onDeleted(context: Context, appWidgetIds: IntArray) {
Logd(TAG, "OnDeleted") Logd(TAG, "OnDeleted")
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
for (appWidgetId in appWidgetIds) { for (appWidgetId in appWidgetIds) {
prefs.edit().remove(KEY_WIDGET_COLOR + appWidgetId).apply() prefs!!.edit().remove(KEY_WIDGET_COLOR + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_PLAYBACK_SPEED + appWidgetId).apply() prefs!!.edit().remove(KEY_WIDGET_PLAYBACK_SPEED + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_REWIND + appWidgetId).apply() prefs!!.edit().remove(KEY_WIDGET_REWIND + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_FAST_FORWARD + appWidgetId).apply() prefs!!.edit().remove(KEY_WIDGET_FAST_FORWARD + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_SKIP + appWidgetId).apply() prefs!!.edit().remove(KEY_WIDGET_SKIP + appWidgetId).apply()
} }
val manager = AppWidgetManager.getInstance(context) val manager = AppWidgetManager.getInstance(context)
val widgetIds = manager.getAppWidgetIds(ComponentName(context, PlayerWidget::class.java)) val widgetIds = manager.getAppWidgetIds(ComponentName(context, PlayerWidget::class.java))
if (widgetIds.isEmpty()) { if (widgetIds.isEmpty()) {
prefs.edit().putBoolean(KEY_WORKAROUND_ENABLED, false).apply() prefs!!.edit().putBoolean(KEY_WORKAROUND_ENABLED, false).apply()
WorkManager.getInstance(context).cancelUniqueWork(WORKAROUND_WORK_NAME) WorkManager.getInstance(context).cancelUniqueWork(WORKAROUND_WORK_NAME)
} }
super.onDeleted(context, appWidgetIds) super.onDeleted(context, appWidgetIds)
} }
private fun setEnabled(context: Context, enabled: Boolean) { private fun setEnabled(context: Context, enabled: Boolean) {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) prefs!!.edit().putBoolean(KEY_ENABLED, enabled).apply()
prefs.edit().putBoolean(KEY_ENABLED, enabled).apply()
} }
companion object { companion object {
@ -75,6 +74,12 @@ class PlayerWidget : AppWidgetProvider() {
const val DEFAULT_COLOR: Int = -0xd9d3cf const val DEFAULT_COLOR: Int = -0xd9d3cf
private const val WORKAROUND_WORK_NAME = "WidgetUpdaterWorkaround" private const val WORKAROUND_WORK_NAME = "WidgetUpdaterWorkaround"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
}
private fun scheduleWorkaround(context: Context) { private fun scheduleWorkaround(context: Context) {
// Enqueueing work enables a BOOT_COMPLETED receiver, which in turn makes Android refresh widgets. // Enqueueing work enables a BOOT_COMPLETED receiver, which in turn makes Android refresh widgets.
// This creates an endless loop with a flickering widget. // This creates an endless loop with a flickering widget.
@ -87,8 +92,8 @@ class PlayerWidget : AppWidgetProvider() {
@JvmStatic @JvmStatic
fun isEnabled(context: Context): Boolean { fun isEnabled(context: Context): Boolean {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) // val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
return prefs.getBoolean(KEY_ENABLED, false) return prefs!!.getBoolean(KEY_ENABLED, false)
} }
} }
} }

View File

@ -9,17 +9,17 @@ import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.storage.model.feed.SortOrder
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
/** /**
* Implementation of the EpisodeCleanupAlgorithm interface used by Podcini. * Implementation of the EpisodeCleanupAlgorithm interface used by Podcini.
*/ */
class APCleanupAlgorithm( /** the number of days after playback to wait before an item is eligible to be cleaned up.
/** the number of days after playback to wait before an item is eligible to be cleaned up. * Fractional for number of hours, e.g., 0.5 = 12 hours, 0.0416 = 1 hour. */
* Fractional for number of hours, e.g., 0.5 = 12 hours, 0.0416 = 1 hour. */
@JvmField @get:VisibleForTesting val numberOfHoursAfterPlayback: Int class APCleanupAlgorithm(@JvmField @get:VisibleForTesting val numberOfHoursAfterPlayback: Int) : EpisodeCleanupAlgorithm() {
) : EpisodeCleanupAlgorithm() {
/** /**
* @return the number of episodes that *could* be cleaned up, if needed * @return the number of episodes that *could* be cleaned up, if needed
*/ */
@ -44,7 +44,7 @@ class APCleanupAlgorithm(
for (item in delete) { for (item in delete) {
try { try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get() runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
@ -53,7 +53,6 @@ class APCleanupAlgorithm(
} }
val counter = delete.size val counter = delete.size
Log.i(TAG, String.format(Locale.US, "Auto-delete deleted %d episodes (%d requested)", counter, numberOfEpisodesToDelete)) Log.i(TAG, String.format(Locale.US, "Auto-delete deleted %d episodes (%d requested)", counter, numberOfEpisodesToDelete))
return counter return counter

View File

@ -6,6 +6,9 @@ import ac.mdiq.podcini.storage.DBReader.getEpisodes
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.storage.model.feed.SortOrder
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
@ -21,7 +24,7 @@ class APQueueCleanupAlgorithm : EpisodeCleanupAlgorithm() {
return candidates.size return candidates.size
} }
public override fun performCleanup(context: Context, numberOfEpisodesToDelete: Int): Int { @OptIn(UnstableApi::class) public override fun performCleanup(context: Context, numberOfEpisodesToDelete: Int): Int {
var candidates = candidates var candidates = candidates
// in the absence of better data, we'll sort by item publication date // in the absence of better data, we'll sort by item publication date
@ -38,8 +41,9 @@ class APQueueCleanupAlgorithm : EpisodeCleanupAlgorithm() {
val delete = if (candidates.size > numberOfEpisodesToDelete) candidates.subList(0, numberOfEpisodesToDelete) else candidates val delete = if (candidates.size > numberOfEpisodesToDelete) candidates.subList(0, numberOfEpisodesToDelete) else candidates
for (item in delete) { for (item in delete) {
if (item.media == null) continue
try { try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get() runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {

View File

@ -25,6 +25,7 @@ import android.text.TextUtils
import android.util.Log import android.util.Log
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import java.util.concurrent.* import java.util.concurrent.*
@ -68,15 +69,13 @@ import java.util.concurrent.*
if (feedID != 0L) { if (feedID != 0L) {
try { try {
DBWriter.deleteFeed(context, feedID).get() runBlocking { DBWriter.deleteFeed(context, feedID).join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
e.printStackTrace() e.printStackTrace()
} }
} else { } else Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: $downloadUrl")
Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: $downloadUrl")
}
} }
/** /**
@ -132,15 +131,13 @@ import java.util.concurrent.*
} }
private fun searchFeedByIdentifyingValueOrID(feed: Feed): Feed? { private fun searchFeedByIdentifyingValueOrID(feed: Feed): Feed? {
if (feed.id != 0L) { if (feed.id != 0L) return getFeed(feed.id)
return getFeed(feed.id)
} else { val feeds = getFeedList()
val feeds = getFeedList() for (f in feeds.toList()) {
for (f in feeds.toList()) { if (f != null && f.identifyingValue == feed.identifyingValue) {
if (f != null && f.identifyingValue == feed.identifyingValue) { f.items = getFeedItemList(f).toMutableList()
f.items = getFeedItemList(f).toMutableList() return f
return f
}
} }
} }
return null return null
@ -275,9 +272,8 @@ import java.util.concurrent.*
} }
} }
if (oldItem != null) { if (oldItem != null) oldItem.updateFromOther(item)
oldItem.updateFromOther(item) else {
} else {
Logd(TAG, "Found new item: " + item.title) Logd(TAG, "Found new item: " + item.title)
item.feed = savedFeed item.feed = savedFeed
@ -314,13 +310,13 @@ import java.util.concurrent.*
try { try {
if (savedFeed == null) { if (savedFeed == null) {
DBWriter.addNewFeed(context, newFeed).get() runBlocking { DBWriter.addNewFeed(context, newFeed).join() }
// Update with default values that are set in database // Update with default values that are set in database
resultFeed = searchFeedByIdentifyingValueOrID(newFeed) resultFeed = searchFeedByIdentifyingValueOrID(newFeed)
} else DBWriter.persistCompleteFeed(savedFeed).get() } else runBlocking { DBWriter.persistCompleteFeed(savedFeed).join() }
DBReader.updateFeedList(adapter) DBReader.updateFeedList(adapter)
if (removeUnlistedItems) DBWriter.deleteFeedItems(context, unlistedItems).get() if (removeUnlistedItems) runBlocking { DBWriter.deleteFeedItems(context, unlistedItems).join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {

View File

@ -6,7 +6,7 @@ import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.net.sync.model.EpisodeAction import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.playback.service.PlaybackServiceConstants import ac.mdiq.podcini.playback.service.PlaybackServiceConstants
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying
import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation import ac.mdiq.podcini.preferences.UserPreferences.enqueueLocation
@ -29,7 +29,6 @@ import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.LongList import ac.mdiq.podcini.util.LongList
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import ac.mdiq.podcini.util.showStackTrace
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
@ -37,16 +36,13 @@ import android.util.Log
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.google.common.util.concurrent.Futures import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import java.io.File import java.io.File
import java.util.* import java.util.*
import java.util.concurrent.ExecutorService import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.ContinuationInterceptor
/** /**
* Provides methods for writing data to Podcini's database. * Provides methods for writing data to Podcini's database.
@ -57,6 +53,8 @@ import java.util.concurrent.TimeUnit
@UnstableApi object DBWriter { @UnstableApi object DBWriter {
private const val TAG = "DBWriter" private const val TAG = "DBWriter"
val ioScope = CoroutineScope(Dispatchers.IO)
private val dbExec: ExecutorService = Executors.newSingleThreadExecutor { r: Runnable? -> private val dbExec: ExecutorService = Executors.newSingleThreadExecutor { r: Runnable? ->
val t = Thread(r) val t = Thread(r)
t.name = "DatabaseExecutor" t.name = "DatabaseExecutor"
@ -77,8 +75,8 @@ import java.util.concurrent.TimeUnit
} }
} }
fun deleteItemsMedia(items: List<FeedItem>) { fun deleteItemsMedia(items: List<FeedItem>) : Job {
runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.removeItemMedia(items) adapter.removeItemMedia(items)
@ -93,7 +91,7 @@ import java.util.concurrent.TimeUnit
* @param mediaId ID of the FeedMedia object whose downloaded file should be deleted. * @param mediaId ID of the FeedMedia object whose downloaded file should be deleted.
*/ */
@JvmStatic @JvmStatic
fun deleteFeedMediaOfItem(context: Context, mediaId: Long): Future<*> { fun deleteFeedMediaOfItem(context: Context, mediaId: Long) : Job {
Logd(TAG, "deleteFeedMediaOfItem called") Logd(TAG, "deleteFeedMediaOfItem called")
return runOnDbThread { return runOnDbThread {
val media = getFeedMedia(mediaId) val media = getFeedMedia(mediaId)
@ -179,7 +177,7 @@ import java.util.concurrent.TimeUnit
* @param feedId ID of the Feed that should be deleted. * @param feedId ID of the Feed that should be deleted.
*/ */
@JvmStatic @JvmStatic
fun deleteFeed(context: Context, feedId: Long): Future<*> { fun deleteFeed(context: Context, feedId: Long) : Job {
return runOnDbThread { return runOnDbThread {
val feed = getFeed(feedId) ?: return@runOnDbThread val feed = getFeed(feedId) ?: return@runOnDbThread
// delete stored media files and mark them as read // delete stored media files and mark them as read
@ -203,7 +201,7 @@ import java.util.concurrent.TimeUnit
* Remove the listed items and their FeedMedia entries. * Remove the listed items and their FeedMedia entries.
* Deleting media also removes the download log entries. * Deleting media also removes the download log entries.
*/ */
fun deleteFeedItems(context: Context, items: List<FeedItem>): Future<*> { fun deleteFeedItems(context: Context, items: List<FeedItem>) : Job {
Logd(TAG, "deleteFeedItems called") Logd(TAG, "deleteFeedItems called")
return runOnDbThread { deleteFeedItemsSynchronous(context, items) } return runOnDbThread { deleteFeedItemsSynchronous(context, items) }
} }
@ -252,7 +250,7 @@ import java.util.concurrent.TimeUnit
/** /**
* Deletes the entire playback history. * Deletes the entire playback history.
*/ */
fun clearPlaybackHistory(): Future<*> { fun clearPlaybackHistory() : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -265,7 +263,7 @@ import java.util.concurrent.TimeUnit
/** /**
* Deletes the entire download log. * Deletes the entire download log.
*/ */
fun clearDownloadLog(): Future<*> { fun clearDownloadLog() : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -275,8 +273,8 @@ import java.util.concurrent.TimeUnit
} }
} }
fun deleteFromPlaybackHistory(feedItem: FeedItem): Future<*> { fun deleteFromPlaybackHistory(feedItem: FeedItem) {
return addItemToPlaybackHistory(feedItem.media, Date(0)) addItemToPlaybackHistory(feedItem.media, Date(0))
} }
/** /**
@ -288,7 +286,7 @@ import java.util.concurrent.TimeUnit
* @param date PlaybackCompletionDate for `media` * @param date PlaybackCompletionDate for `media`
*/ */
@JvmOverloads @JvmOverloads
fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> { fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()) : Job {
return runOnDbThread { return runOnDbThread {
if (media != null) { if (media != null) {
Logd(TAG, "Adding item to playback history") Logd(TAG, "Adding item to playback history")
@ -308,7 +306,7 @@ import java.util.concurrent.TimeUnit
* *
* @param status The DownloadStatus object. * @param status The DownloadStatus object.
*/ */
fun addDownloadStatus(status: DownloadResult?): Future<*> { fun addDownloadStatus(status: DownloadResult?) : Job {
Logd(TAG, "addDownloadStatus called") Logd(TAG, "addDownloadStatus called")
return runOnDbThread { return runOnDbThread {
if (status != null) { if (status != null) {
@ -331,7 +329,7 @@ import java.util.concurrent.TimeUnit
* @param performAutoDownload True if an auto-download process should be started after the operation * @param performAutoDownload True if an auto-download process should be started after the operation
* @throws IndexOutOfBoundsException if index < 0 || index >= queue.size() * @throws IndexOutOfBoundsException if index < 0 || index >= queue.size()
*/ */
@UnstableApi fun addQueueItemAt(context: Context, itemId: Long, index: Int, performAutoDownload: Boolean): Future<*> { @UnstableApi fun addQueueItemAt(context: Context, itemId: Long, index: Int, performAutoDownload: Boolean) : Job {
Logd(TAG, "addQueueItemAt called") Logd(TAG, "addQueueItemAt called")
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
@ -357,11 +355,11 @@ import java.util.concurrent.TimeUnit
} }
@JvmStatic @JvmStatic
fun addQueueItem(context: Context, vararg items: FeedItem): Future<*> { fun addQueueItem(context: Context, vararg items: FeedItem) : Job {
return addQueueItem(context, true, *items) return addQueueItem(context, true, *items)
} }
fun addQueueItem(context: Context, markAsUnplayed: Boolean, vararg items: FeedItem): Future<*> { fun addQueueItem(context: Context, markAsUnplayed: Boolean, vararg items: FeedItem) : Job {
Logd(TAG, "addQueueItem called") Logd(TAG, "addQueueItem called")
val itemIds = LongList(items.size) val itemIds = LongList(items.size)
for (item in items) { for (item in items) {
@ -380,7 +378,7 @@ import java.util.concurrent.TimeUnit
* @param performAutoDownload true if an auto-download process should be started after the operation. * @param performAutoDownload true if an auto-download process should be started after the operation.
* @param itemIds IDs of the FeedItem objects that should be added to the queue. * @param itemIds IDs of the FeedItem objects that should be added to the queue.
*/ */
@UnstableApi fun addQueueItem(context: Context, performAutoDownload: Boolean, vararg itemIds: Long): Future<*> { @UnstableApi fun addQueueItem(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) : Job {
return addQueueItem(context, performAutoDownload, true, *itemIds) return addQueueItem(context, performAutoDownload, true, *itemIds)
} }
@ -393,7 +391,7 @@ import java.util.concurrent.TimeUnit
* @param markAsUnplayed true if the items should be marked as unplayed when enqueueing * @param markAsUnplayed true if the items should be marked as unplayed when enqueueing
* @param itemIds IDs of the FeedItem objects that should be added to the queue. * @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<*> { @UnstableApi fun addQueueItem(context: Context, performAutoDownload: Boolean, markAsUnplayed: Boolean, vararg itemIds: Long) : Job {
Logd(TAG, "addQueueItem(context ...) called") Logd(TAG, "addQueueItem(context ...) called")
return runOnDbThread { return runOnDbThread {
if (itemIds.isEmpty()) return@runOnDbThread if (itemIds.isEmpty()) return@runOnDbThread
@ -406,9 +404,8 @@ import java.util.concurrent.TimeUnit
val markAsUnplayedIds = LongList() val markAsUnplayedIds = LongList()
val events: MutableList<FlowEvent.QueueEvent> = ArrayList() val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
val updatedItems: MutableList<FeedItem> = ArrayList() val updatedItems: MutableList<FeedItem> = ArrayList()
val positionCalculator = val positionCalculator = ItemEnqueuePositionCalculator(enqueueLocation)
ItemEnqueuePositionCalculator(enqueueLocation) val currentlyPlaying = loadPlayableFromPreferences()
val currentlyPlaying = createInstanceFromPreferences(context)
var insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying) var insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying)
for (itemId in itemIds) { for (itemId in itemIds) {
if (!itemListContains(queue, itemId)) { if (!itemListContains(queue, itemId)) {
@ -468,7 +465,7 @@ import java.util.concurrent.TimeUnit
* Removes all FeedItem objects from the queue. * Removes all FeedItem objects from the queue.
*/ */
@JvmStatic @JvmStatic
fun clearQueue(): Future<*> { fun clearQueue() : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -486,19 +483,19 @@ import java.util.concurrent.TimeUnit
* @param item FeedItem that should be removed. * @param item FeedItem that should be removed.
*/ */
@JvmStatic @JvmStatic
fun removeQueueItem(context: Context, performAutoDownload: Boolean, item: FeedItem): Future<*> { fun removeQueueItem(context: Context, performAutoDownload: Boolean, item: FeedItem) : Job {
return runOnDbThread { removeQueueItemSynchronous(context, performAutoDownload, item.id) } return runOnDbThread { removeQueueItemSynchronous(context, performAutoDownload, item.id) }
} }
@JvmStatic @JvmStatic
fun removeQueueItem(context: Context, performAutoDownload: Boolean, vararg itemIds: Long): Future<*> { fun removeQueueItem(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) : Job {
return runOnDbThread { removeQueueItemSynchronous(context, performAutoDownload, *itemIds) } return runOnDbThread { removeQueueItemSynchronous(context, performAutoDownload, *itemIds) }
} }
@UnstableApi private fun removeQueueItemSynchronous(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) { @UnstableApi private fun removeQueueItemSynchronous(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) {
Logd(TAG, "removeQueueItemSynchronous called $itemIds") Logd(TAG, "removeQueueItemSynchronous called $itemIds")
if (itemIds.isEmpty()) return if (itemIds.isEmpty()) return
showStackTrace() // showStackTrace()
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -535,11 +532,12 @@ import java.util.concurrent.TimeUnit
if (performAutoDownload) autodownloadUndownloadedItems(context) if (performAutoDownload) autodownloadUndownloadedItems(context)
} }
fun toggleFavoriteItem(item: FeedItem): Future<*> {
return if (item.isTagged(FeedItem.TAG_FAVORITE)) removeFavoriteItem(item) else addFavoriteItem(item) fun toggleFavoriteItem(item: FeedItem) {
if (item.isTagged(FeedItem.TAG_FAVORITE)) removeFavoriteItem(item) else addFavoriteItem(item)
} }
fun addFavoriteItem(item: FeedItem): Future<*> { fun addFavoriteItem(item: FeedItem) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance().open() val adapter = getInstance().open()
adapter.addFavoriteItem(item) adapter.addFavoriteItem(item)
@ -550,7 +548,7 @@ import java.util.concurrent.TimeUnit
} }
} }
fun removeFavoriteItem(item: FeedItem): Future<*> { fun removeFavoriteItem(item: FeedItem) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance().open() val adapter = getInstance().open()
adapter.removeFavoriteItem(item) adapter.removeFavoriteItem(item)
@ -567,7 +565,7 @@ import java.util.concurrent.TimeUnit
* @param itemId The item to move to the top of the queue * @param itemId The item to move to the top of the queue
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/ */
fun moveQueueItemToTop(itemId: Long, broadcastUpdate: Boolean): Future<*> { fun moveQueueItemToTop(itemId: Long, broadcastUpdate: Boolean) : Job {
return runOnDbThread { return runOnDbThread {
val queueIdList = getQueueIDList() val queueIdList = getQueueIDList()
val index = queueIdList.indexOf(itemId) val index = queueIdList.indexOf(itemId)
@ -582,7 +580,7 @@ import java.util.concurrent.TimeUnit
* @param itemId The item to move to the bottom of the queue * @param itemId The item to move to the bottom of the queue
* @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to * @param broadcastUpdate true if this operation should trigger a QueueUpdateBroadcast. This option should be set to
*/ */
fun moveQueueItemToBottom(itemId: Long, broadcastUpdate: Boolean): Future<*> { fun moveQueueItemToBottom(itemId: Long, broadcastUpdate: Boolean) : Job {
return runOnDbThread { return runOnDbThread {
val queueIdList = getQueueIDList() val queueIdList = getQueueIDList()
val index = queueIdList.indexOf(itemId) val index = queueIdList.indexOf(itemId)
@ -601,7 +599,7 @@ import java.util.concurrent.TimeUnit
* @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size())
*/ */
@JvmStatic @JvmStatic
fun moveQueueItem(from: Int, to: Int, broadcastUpdate: Boolean): Future<*> { fun moveQueueItem(from: Int, to: Int, broadcastUpdate: Boolean) : Job {
return runOnDbThread { moveQueueItemHelper(from, to, broadcastUpdate) } return runOnDbThread { moveQueueItemHelper(from, to, broadcastUpdate) }
} }
@ -634,7 +632,7 @@ import java.util.concurrent.TimeUnit
adapter.close() adapter.close()
} }
fun resetPagedFeedPage(feed: Feed?): Future<*> { fun resetPagedFeedPage(feed: Feed?) : Job {
return runOnDbThread { return runOnDbThread {
if (feed != null) { if (feed != null) {
val adapter = getInstance() val adapter = getInstance()
@ -652,7 +650,7 @@ import java.util.concurrent.TimeUnit
* FeedItem.UNPLAYED * FeedItem.UNPLAYED
* @param itemIds IDs of the FeedItems. * @param itemIds IDs of the FeedItems.
*/ */
fun markItemPlayed(played: Int, vararg itemIds: Long): Future<*> { fun markItemPlayed(played: Int, vararg itemIds: Long) : Job {
return markItemPlayed(played, true, *itemIds) return markItemPlayed(played, true, *itemIds)
} }
@ -665,7 +663,7 @@ import java.util.concurrent.TimeUnit
* This option is usually set to true * This option is usually set to true
* @param itemIds IDs of the FeedItems. * @param itemIds IDs of the FeedItems.
*/ */
fun markItemPlayed(played: Int, broadcastUpdate: Boolean, vararg itemIds: Long): Future<*> { fun markItemPlayed(played: Int, broadcastUpdate: Boolean, vararg itemIds: Long) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -683,12 +681,12 @@ import java.util.concurrent.TimeUnit
* FeedItem.NEW, FeedItem.UNPLAYED * FeedItem.NEW, FeedItem.UNPLAYED
* @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object. * @param resetMediaPosition true if this method should also reset the position of the FeedItem's FeedMedia object.
*/ */
fun markItemPlayed(item: FeedItem, played: Int, resetMediaPosition: Boolean): Future<*> { fun markItemPlayed(item: FeedItem, played: Int, resetMediaPosition: Boolean) : Job {
val mediaId = if (item.media != null) item.media!!.id else 0 val mediaId = if (item.media != null) item.media!!.id else 0
return markItemPlayed(item.id, played, mediaId, resetMediaPosition) return markItemPlayed(item.id, played, mediaId, resetMediaPosition)
} }
private fun markItemPlayed(itemId: Long, played: Int, mediaId: Long, resetMediaPosition: Boolean): Future<*> { private fun markItemPlayed(itemId: Long, played: Int, mediaId: Long, resetMediaPosition: Boolean) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -703,7 +701,7 @@ import java.util.concurrent.TimeUnit
* *
* @param feedId ID of the Feed. * @param feedId ID of the Feed.
*/ */
fun removeFeedNewFlag(feedId: Long): Future<*> { fun removeFeedNewFlag(feedId: Long) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -717,7 +715,7 @@ import java.util.concurrent.TimeUnit
* Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED. * Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
*/ */
@JvmStatic @JvmStatic
fun removeAllNewFlags(): Future<*> { fun removeAllNewFlags() : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -727,7 +725,7 @@ import java.util.concurrent.TimeUnit
} }
} }
fun addNewFeed(context: Context, vararg feeds: Feed): Future<*> { fun addNewFeed(context: Context, vararg feeds: Feed) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -744,7 +742,7 @@ import java.util.concurrent.TimeUnit
} }
} }
fun persistCompleteFeed(vararg feeds: Feed): Future<*> { fun persistCompleteFeed(vararg feeds: Feed) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -753,7 +751,7 @@ import java.util.concurrent.TimeUnit
} }
} }
fun persistItemList(items: List<FeedItem>): Future<*> { fun persistItemList(items: List<FeedItem>) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -769,7 +767,7 @@ import java.util.concurrent.TimeUnit
* *
* @param media The FeedMedia object. * @param media The FeedMedia object.
*/ */
fun persistFeedMedia(media: FeedMedia): Future<*> { fun persistFeedMedia(media: FeedMedia) : Job {
Logd(TAG, "persistFeedMedia called") Logd(TAG, "persistFeedMedia called")
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
@ -785,7 +783,7 @@ import java.util.concurrent.TimeUnit
* @param media The FeedMedia object. * @param media The FeedMedia object.
*/ */
@JvmStatic @JvmStatic
fun persistFeedMediaPlaybackInfo(media: FeedMedia?): Future<*> { fun persistFeedMediaPlaybackInfo(media: FeedMedia?) : Job {
Logd(TAG, "persistFeedMediaPlaybackInfo called") Logd(TAG, "persistFeedMediaPlaybackInfo called")
return runOnDbThread { return runOnDbThread {
if (media != null) { if (media != null) {
@ -804,7 +802,7 @@ import java.util.concurrent.TimeUnit
* @param item The FeedItem object. * @param item The FeedItem object.
*/ */
@JvmStatic @JvmStatic
fun persistFeedItem(item: FeedItem?): Future<*> { fun persistFeedItem(item: FeedItem?) : Job {
Logd(TAG, "persistFeedItem called") Logd(TAG, "persistFeedItem called")
return runOnDbThread { return runOnDbThread {
if (item != null) { if (item != null) {
@ -820,7 +818,7 @@ import java.util.concurrent.TimeUnit
/** /**
* Updates download URL of a feed * Updates download URL of a feed
*/ */
fun updateFeedDownloadURL(original: String, updated: String): Future<*> { fun updateFeedDownloadURL(original: String, updated: String) : Job {
Logd(TAG, "updateFeedDownloadURL(original: $original, updated: $updated)") Logd(TAG, "updateFeedDownloadURL(original: $original, updated: $updated)")
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
@ -835,7 +833,7 @@ import java.util.concurrent.TimeUnit
* *
* @param preferences The FeedPreferences object. * @param preferences The FeedPreferences object.
*/ */
fun persistFeedPreferences(preferences: FeedPreferences): Future<*> { fun persistFeedPreferences(preferences: FeedPreferences) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -863,7 +861,7 @@ import java.util.concurrent.TimeUnit
* *
* @param lastUpdateFailed true if last update failed * @param lastUpdateFailed true if last update failed
*/ */
fun persistFeedLastUpdateFailed(feedId: Long, lastUpdateFailed: Boolean): Future<*> { fun persistFeedLastUpdateFailed(feedId: Long, lastUpdateFailed: Boolean) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -873,7 +871,7 @@ import java.util.concurrent.TimeUnit
} }
} }
fun persistFeedCustomTitle(feed: Feed): Future<*> { fun persistFeedCustomTitle(feed: Feed) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -890,10 +888,10 @@ import java.util.concurrent.TimeUnit
* QueueUpdateBroadcast. This option should be set to `false` * QueueUpdateBroadcast. This option should be set to `false`
* if the caller wants to avoid unexpected updates of the GUI. * if the caller wants to avoid unexpected updates of the GUI.
*/ */
fun reorderQueue(sortOrder: SortOrder?, broadcastUpdate: Boolean): Future<*> { fun reorderQueue(sortOrder: SortOrder?, broadcastUpdate: Boolean) : Job {
if (sortOrder == null) { if (sortOrder == null) {
Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.") Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.")
return runOnDbThread {} return Job()
} }
val permutor = getPermutor(sortOrder) val permutor = getPermutor(sortOrder)
return runOnDbThread { return runOnDbThread {
@ -914,7 +912,7 @@ import java.util.concurrent.TimeUnit
* @param feedId The feed's ID * @param feedId The feed's ID
* @param filterValues Values that represent properties to filter by * @param filterValues Values that represent properties to filter by
*/ */
fun persistFeedItemsFilter(feedId: Long, filterValues: Set<String>): Future<*> { fun persistFeedItemsFilter(feedId: Long, filterValues: Set<String>) : Job {
Logd(TAG, "persistFeedItemsFilter() called with: feedId = [$feedId], filterValues = [$filterValues]") Logd(TAG, "persistFeedItemsFilter() called with: feedId = [$feedId], filterValues = [$filterValues]")
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
@ -929,7 +927,7 @@ import java.util.concurrent.TimeUnit
* Set item sort order of the feed * Set item sort order of the feed
* *
*/ */
fun persistFeedItemSortOrder(feedId: Long, sortOrder: SortOrder?): Future<*> { fun persistFeedItemSortOrder(feedId: Long, sortOrder: SortOrder?) : Job {
return runOnDbThread { return runOnDbThread {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
@ -942,32 +940,36 @@ import java.util.concurrent.TimeUnit
/** /**
* Reset the statistics in DB * Reset the statistics in DB
*/ */
// fun resetStatistics(): Future<*> { fun resetStatistics() : Job {
// return runOnDbThread { return runOnDbThread {
// val adapter = getInstance()
// adapter.open()
// adapter.resetAllMediaPlayedDuration()
// adapter.close()
// }
// }
suspend fun resetStatistics(): Unit = withContext(Dispatchers.IO) {
val result = async {
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
adapter.resetAllMediaPlayedDuration() adapter.resetAllMediaPlayedDuration()
adapter.close() adapter.close()
} }
result.await()
} }
/** /**
* Submit to the DB thread only if caller is not already on the DB thread. Otherwise, * Submit to the DB thread only if caller is not already on the DB thread. Otherwise,
* just execute synchronously * just execute synchronously
*/ */
private fun runOnDbThread(runnable: Runnable): Future<*> { // private fun runOnDbThread(runnable: Runnable): Future<*> {
if ("DatabaseExecutor" == Thread.currentThread().name) { // if ("DatabaseExecutor" == Thread.currentThread().name) {
runnable.run() // runnable.run()
return Futures.immediateFuture<Any?>(null) // return Futures.immediateFuture<Any?>(null)
} else return dbExec.submit(runnable) // } else return dbExec.submit(runnable)
// }
private fun runOnDbThread(block: suspend () -> Unit) : Job {
Logd(TAG, "DBWriter runOnDbThread")
return ioScope.launch {
if (Dispatchers.IO == coroutineContext[ContinuationInterceptor]) {
block()
} else {
withContext(Dispatchers.IO) {
block()
}
}
}
} }
} }

View File

@ -11,6 +11,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
@ -41,8 +42,9 @@ class ExceptFavoriteCleanupAlgorithm : EpisodeCleanupAlgorithm() {
val delete = if (candidates.size > numberOfEpisodesToDelete) candidates.subList(0, numberOfEpisodesToDelete) else candidates val delete = if (candidates.size > numberOfEpisodesToDelete) candidates.subList(0, numberOfEpisodesToDelete) else candidates
for (item in delete) { for (item in delete) {
if (item.media == null) continue
try { try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get() runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {

View File

@ -105,7 +105,7 @@ class Feed : FeedFile {
var sortOrder: SortOrder? = null var sortOrder: SortOrder? = null
set(sortOrder) { set(sortOrder) {
if (sortOrder == null) { if (sortOrder == null) {
Log.w("Feed sortOrder", "The specified sortOrder $sortOrder is invalid.") // Log.w("Feed sortOrder", "The specified sortOrder $sortOrder is invalid.")
return return
} }
field = sortOrder field = sortOrder

View File

@ -9,7 +9,7 @@ import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.util.LongList import ac.mdiq.podcini.util.LongList
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.ui.view.LocalDeleteModal import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@UnstableApi @UnstableApi

View File

@ -5,7 +5,7 @@ import android.view.View
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.ui.view.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary import ac.mdiq.podcini.ui.utils.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
class DeleteActionButton(item: FeedItem) : ItemActionButton(item) { class DeleteActionButton(item: FeedItem) : ItemActionButton(item) {

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.ui.actions.menuhandler package ac.mdiq.podcini.ui.actions.menuhandler
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.net.sync.SynchronizationSettings
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
import ac.mdiq.podcini.net.sync.model.EpisodeAction import ac.mdiq.podcini.net.sync.model.EpisodeAction
@ -14,16 +13,19 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.ShareDialog import ac.mdiq.podcini.ui.dialog.ShareDialog
import ac.mdiq.podcini.ui.view.LocalDeleteModal import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import ac.mdiq.podcini.util.* import ac.mdiq.podcini.util.*
import android.os.Handler import android.os.Handler
import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.ceil import kotlin.math.ceil
@ -49,7 +51,6 @@ object FeedItemMenuHandler {
val hasMedia = selectedItem.media != null val hasMedia = selectedItem.media != null
val isPlaying = hasMedia && PlaybackStatus.isPlaying(selectedItem.media) val isPlaying = hasMedia && PlaybackStatus.isPlaying(selectedItem.media)
val isInQueue: Boolean = selectedItem.isTagged(FeedItem.TAG_QUEUE) val isInQueue: Boolean = selectedItem.isTagged(FeedItem.TAG_QUEUE)
val fileDownloaded = hasMedia && selectedItem.media?.fileExists()?:false
val isLocalFile = hasMedia && selectedItem.feed?.isLocalFeed?:false val isLocalFile = hasMedia && selectedItem.feed?.isLocalFeed?:false
val isFavorite: Boolean = selectedItem.isTagged(FeedItem.TAG_FAVORITE) val isFavorite: Boolean = selectedItem.isTagged(FeedItem.TAG_FAVORITE)
@ -73,7 +74,11 @@ object FeedItemMenuHandler {
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite) setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite)
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite) setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite)
setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile)
CoroutineScope(Dispatchers.Main).launch {
val fileDownloaded = withContext(Dispatchers.IO) { hasMedia && selectedItem.media?.fileExists() ?: false }
setItemVisibility(menu, R.id.remove_item, fileDownloaded || isLocalFile)
}
return true return true
} }

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.ui.view.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary import ac.mdiq.podcini.ui.utils.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary
class DeleteSwipeAction : SwipeAction { class DeleteSwipeAction : SwipeAction {
override fun getId(): String { override fun getId(): String {

View File

@ -3,18 +3,21 @@ package ac.mdiq.podcini.ui.actions.swipeactions
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment
import ac.mdiq.podcini.ui.fragment.DownloadsFragment
import ac.mdiq.podcini.ui.fragment.HistoryFragment
import ac.mdiq.podcini.ui.fragment.QueueFragment
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context import android.content.Context
import android.content.SharedPreferences
import android.graphics.Canvas import android.graphics.Canvas
import androidx.annotation.OptIn
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle import androidx.lifecycle.*
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@ -26,7 +29,7 @@ import kotlin.math.min
import kotlin.math.sin import kotlin.math.sin
open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private val tag: String) : open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private val tag: String) :
ItemTouchHelper.SimpleCallback(dragDirs, ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT), LifecycleObserver { ItemTouchHelper.SimpleCallback(dragDirs, ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT), DefaultLifecycleObserver {
private var filter: FeedItemFilter? = null private var filter: FeedItemFilter? = null
@ -36,17 +39,19 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
private val itemTouchHelper = ItemTouchHelper(this) private val itemTouchHelper = ItemTouchHelper(this)
init { init {
reloadPreference() actions = getPrefs(fragment.requireContext(), tag)
fragment.lifecycle.addObserver(this)
} }
constructor(fragment: Fragment, tag: String) : this(0, fragment, tag) constructor(fragment: Fragment, tag: String) : this(0, fragment, tag)
@OnLifecycleEvent(Lifecycle.Event.ON_START) override fun onStart(owner: LifecycleOwner) {
fun reloadPreference() {
actions = getPrefs(fragment.requireContext(), tag) actions = getPrefs(fragment.requireContext(), tag)
} }
override fun onStop(owner: LifecycleOwner) {
actions = null
}
fun setFilter(filter: FeedItemFilter?) { fun setFilter(filter: FeedItemFilter?) {
this.filter = filter this.filter = filter
} }
@ -69,19 +74,10 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
@UnstableApi override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) { @UnstableApi override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
if (actions != null && !actions!!.hasActions()) { if (actions != null && !actions!!.hasActions()) {
//open settings dialog if no prefs are set
showDialog() showDialog()
// SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
// override fun onCall() {
// this@SwipeActions.reloadPreference()
// EventBus.getDefault().post(SwipeActionsChangedEvent())
// }
// })
return return
} }
val item = (viewHolder as EpisodeItemViewHolder).feedItem val item = (viewHolder as EpisodeItemViewHolder).feedItem
if (actions != null && item != null && filter != null) if (actions != null && item != null && filter != null)
(if (swipeDir == ItemTouchHelper.RIGHT) actions!!.right else actions!!.left)?.performAction(item, fragment, filter!!) (if (swipeDir == ItemTouchHelper.RIGHT) actions!!.right else actions!!.left)?.performAction(item, fragment, filter!!)
} }
@ -89,7 +85,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
fun showDialog() { fun showDialog() {
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback { SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
override fun onCall() { override fun onCall() {
this@SwipeActions.reloadPreference() actions = getPrefs(fragment.requireContext(), tag)
EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent()) EventFlow.postEvent(FlowEvent.SwipeActionsChangedEvent())
} }
}) })
@ -124,12 +120,10 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && wontLeave) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE && wontLeave) {
swipeOutEnabled = false swipeOutEnabled = false
val swipeThresholdReached = displacementPercentage == 1f val swipeThresholdReached = displacementPercentage == 1f
// Move slower when getting near the maxMovement // Move slower when getting near the maxMovement
dx = sign * maxMovement * sin((Math.PI / 2) * displacementPercentage) dx = sign * maxMovement * sin((Math.PI / 2) * displacementPercentage).toFloat()
.toFloat()
if (isCurrentlyActive) { if (isCurrentlyActive) {
val dir = if (dx > 0) ItemTouchHelper.RIGHT else ItemTouchHelper.LEFT val dir = if (dx > 0) ItemTouchHelper.RIGHT else ItemTouchHelper.LEFT
@ -202,10 +196,16 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
} }
companion object { companion object {
const val PREF_NAME: String = "SwipeActionsPrefs" const val SWIPE_ACTIONS_PREF_NAME: String = "SwipeActionsPrefs"
const val KEY_PREFIX_SWIPEACTIONS: String = "PrefSwipeActions" const val KEY_PREFIX_SWIPEACTIONS: String = "PrefSwipeActions"
const val KEY_PREFIX_NO_ACTION: String = "PrefNoSwipeAction" const val KEY_PREFIX_NO_ACTION: String = "PrefNoSwipeAction"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(SWIPE_ACTIONS_PREF_NAME, Context.MODE_PRIVATE)
}
@JvmField @JvmField
val swipeActions: List<SwipeAction> = Collections.unmodifiableList( val swipeActions: List<SwipeAction> = Collections.unmodifiableList(
listOf(NoActionSwipeAction(), AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(), listOf(NoActionSwipeAction(), AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(),
@ -214,9 +214,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
) )
private fun getPrefs(context: Context, tag: String, defaultActions: String): Actions { private fun getPrefs(context: Context, tag: String, defaultActions: String): Actions {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) val prefsString = prefs!!.getString(KEY_PREFIX_SWIPEACTIONS + tag, defaultActions)
val prefsString = prefs.getString(KEY_PREFIX_SWIPEACTIONS + tag, defaultActions)
return Actions(prefsString) return Actions(prefsString)
} }
@ -224,12 +222,12 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
return getPrefs(context, tag, "") return getPrefs(context, tag, "")
} }
@JvmStatic @OptIn(UnstableApi::class) @JvmStatic
fun getPrefsWithDefaults(context: Context, tag: String): Actions { fun getPrefsWithDefaults(context: Context, tag: String): Actions {
val defaultActions = when (tag) { val defaultActions = when (tag) {
QueueFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION QueueFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
DownloadsFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION DownloadsFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
PlaybackHistoryFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION HistoryFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
AllEpisodesFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION AllEpisodesFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
else -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION else -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
} }
@ -238,8 +236,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
@JvmStatic @JvmStatic
fun isSwipeActionEnabled(context: Context, tag: String): Boolean { fun isSwipeActionEnabled(context: Context, tag: String): Boolean {
val prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) return prefs!!.getBoolean(KEY_PREFIX_NO_ACTION + tag, true)
return prefs.getBoolean(KEY_PREFIX_NO_ACTION + tag, true)
} }
} }
} }

View File

@ -3,6 +3,7 @@ package ac.mdiq.podcini.ui.activity
import ac.mdiq.podcini.BuildConfig import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.MainActivityBinding import ac.mdiq.podcini.databinding.MainActivityBinding
import ac.mdiq.podcini.net.discovery.ItunesTopListLoader
import ac.mdiq.podcini.net.download.FeedUpdateManager import ac.mdiq.podcini.net.download.FeedUpdateManager
import ac.mdiq.podcini.net.download.FeedUpdateManager.restartUpdateAlarm import ac.mdiq.podcini.net.download.FeedUpdateManager.restartUpdateAlarm
import ac.mdiq.podcini.net.download.FeedUpdateManager.runOnceOrAsk import ac.mdiq.podcini.net.download.FeedUpdateManager.runOnceOrAsk
@ -16,14 +17,19 @@ import ac.mdiq.podcini.preferences.UserPreferences.backButtonOpensDrawer
import ac.mdiq.podcini.preferences.UserPreferences.defaultPage import ac.mdiq.podcini.preferences.UserPreferences.defaultPage
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createIntent import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createIntent
import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter.ioScope
import ac.mdiq.podcini.storage.model.download.DownloadStatus import ac.mdiq.podcini.storage.model.download.DownloadStatus
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
import ac.mdiq.podcini.ui.dialog.RatingDialog import ac.mdiq.podcini.ui.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.utils.ThemeUtils.getDrawableFromAttr import ac.mdiq.podcini.ui.utils.ThemeUtils.getDrawableFromAttr
import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior import ac.mdiq.podcini.ui.utils.TransitionEffect
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.utils.LockableBottomSheetBehavior
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
@ -110,7 +116,18 @@ class MainActivity : CastEnabledActivity() {
StrictMode.setThreadPolicy(builder.build()) StrictMode.setThreadPolicy(builder.build())
} }
DBReader.updateFeedList() ioScope.launch {
NavDrawerFragment.getSharedPrefs(this@MainActivity)
SwipeActions.getSharedPrefs(this@MainActivity)
QueueFragment.getSharedPrefs(this@MainActivity)
DBReader.updateFeedList()
EpisodeItemListRecyclerView.getSharedPrefs(this@MainActivity)
PlayerDetailsFragment.getSharedPrefs(this@MainActivity)
PlayerWidget.getSharedPrefs(this@MainActivity)
StatisticsFragment.getSharedPrefs(this@MainActivity)
OnlineFeedViewFragment.getSharedPrefs(this@MainActivity)
ItunesTopListLoader.getSharedPrefs(this@MainActivity)
}
if (savedInstanceState != null) ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(KEY_GENERATED_VIEW_ID, 0)) if (savedInstanceState != null) ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(KEY_GENERATED_VIEW_ID, 0))
@ -145,9 +162,8 @@ class MainActivity : CastEnabledActivity() {
val fm = supportFragmentManager val fm = supportFragmentManager
if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) { if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) {
if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) { if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) loadFragment(defaultPage, null)
loadFragment(defaultPage, null) else {
} else {
val lastFragment = NavDrawerFragment.getLastNavFragment(this) val lastFragment = NavDrawerFragment.getLastNavFragment(this)
if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) { if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) {
loadFragment(lastFragment, null) loadFragment(lastFragment, null)
@ -173,14 +189,15 @@ class MainActivity : CastEnabledActivity() {
navDrawer = findViewById(R.id.navDrawerFragment) navDrawer = findViewById(R.id.navDrawerFragment)
audioPlayerFragmentView = findViewById(R.id.audioplayerFragment) audioPlayerFragmentView = findViewById(R.id.audioplayerFragment)
checkFirstLaunch() ioScope.launch { checkFirstLaunch() }
this.bottomSheet = BottomSheetBehavior.from(audioPlayerFragmentView) as LockableBottomSheetBehavior<*> this.bottomSheet = BottomSheetBehavior.from(audioPlayerFragmentView) as LockableBottomSheetBehavior<*>
this.bottomSheet.isHideable = false this.bottomSheet.isHideable = false
this.bottomSheet.isDraggable = false this.bottomSheet.isDraggable = false
this.bottomSheet.setBottomSheetCallback(bottomSheetCallback) this.bottomSheet.setBottomSheetCallback(bottomSheetCallback)
restartUpdateAlarm(this, false) restartUpdateAlarm(this, false)
SynchronizationQueueSink.syncNowIfNotSyncedRecently() ioScope.launch { SynchronizationQueueSink.syncNowIfNotSyncedRecently() }
WorkManager.getInstance(this) WorkManager.getInstance(this)
.getWorkInfosByTagLiveData(FeedUpdateManager.WORK_TAG_FEED_UPDATE) .getWorkInfosByTagLiveData(FeedUpdateManager.WORK_TAG_FEED_UPDATE)
@ -376,7 +393,7 @@ class MainActivity : CastEnabledActivity() {
QueueFragment.TAG -> fragment = QueueFragment() QueueFragment.TAG -> fragment = QueueFragment()
AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment() AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
DownloadsFragment.TAG -> fragment = DownloadsFragment() DownloadsFragment.TAG -> fragment = DownloadsFragment()
PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment() HistoryFragment.TAG -> fragment = HistoryFragment()
AddFeedFragment.TAG -> fragment = AddFeedFragment() AddFeedFragment.TAG -> fragment = AddFeedFragment()
SubscriptionFragment.TAG -> fragment = SubscriptionFragment() SubscriptionFragment.TAG -> fragment = SubscriptionFragment()
StatisticsFragment.TAG -> fragment = StatisticsFragment() StatisticsFragment.TAG -> fragment = StatisticsFragment()
@ -389,7 +406,7 @@ class MainActivity : CastEnabledActivity() {
} }
if (args != null) fragment.arguments = args if (args != null) fragment.arguments = args
NavDrawerFragment.saveLastNavFragment(this, tag) ioScope.launch { NavDrawerFragment.saveLastNavFragment(this@MainActivity, tag) }
loadFragment(fragment) loadFragment(fragment)
} }
@ -560,6 +577,7 @@ class MainActivity : CastEnabledActivity() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.MessageEvent -> onEventMainThread(event) is FlowEvent.MessageEvent -> onEventMainThread(event)
else -> {} else -> {}
@ -660,7 +678,7 @@ class MainActivity : CastEnabledActivity() {
val feature = uri.getQueryParameter("page") ?: return val feature = uri.getQueryParameter("page") ?: return
when (feature) { when (feature) {
"DOWNLOADS" -> loadFragment(DownloadsFragment.TAG, null) "DOWNLOADS" -> loadFragment(DownloadsFragment.TAG, null)
"HISTORY" -> loadFragment(PlaybackHistoryFragment.TAG, null) "HISTORY" -> loadFragment(HistoryFragment.TAG, null)
"EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null) "EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null)
"QUEUE" -> loadFragment(QueueFragment.TAG, null) "QUEUE" -> loadFragment(QueueFragment.TAG, null)
"SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null) "SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null)

View File

@ -161,6 +161,7 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd("PreferenceActivity", "Received event: ${event}")
when (event) { when (event) {
is FlowEvent.MessageEvent -> onEventMainThread(event) is FlowEvent.MessageEvent -> onEventMainThread(event)
else -> {} else -> {}

View File

@ -13,6 +13,8 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.dialog.* import ac.mdiq.podcini.ui.dialog.*
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.InternalPlayerFragment
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.InternalPlayerFragment.Companion
import ac.mdiq.podcini.ui.fragment.ChaptersFragment import ac.mdiq.podcini.ui.fragment.ChaptersFragment
import ac.mdiq.podcini.ui.fragment.VideoEpisodeFragment import ac.mdiq.podcini.ui.fragment.VideoEpisodeFragment
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
@ -169,6 +171,7 @@ class VideoplayerActivity : CastEnabledActivity() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) supportInvalidateOptionsMenu() is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) supportInvalidateOptionsMenu()
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) finish() is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) finish()

View File

@ -16,6 +16,7 @@ import ac.mdiq.podcini.databinding.ActivityWidgetConfigBinding
import ac.mdiq.podcini.databinding.PlayerWidgetBinding import ac.mdiq.podcini.databinding.PlayerWidgetBinding
import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme
import ac.mdiq.podcini.receiver.PlayerWidget import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
class WidgetConfigActivity : AppCompatActivity() { class WidgetConfigActivity : AppCompatActivity() {
@ -95,13 +96,13 @@ class WidgetConfigActivity : AppCompatActivity() {
} }
private fun setInitialState() { private fun setInitialState() {
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE) // val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
ckPlaybackSpeed.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, true) ckPlaybackSpeed.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, true)
ckRewind.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, true) ckRewind.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, true)
ckFastForward.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, true) ckFastForward.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, true)
ckSkip.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, true) ckSkip.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val color = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, PlayerWidget.DEFAULT_COLOR) val color = prefs!!.getInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, PlayerWidget.DEFAULT_COLOR)
val opacity = Color.alpha(color) * 100 / 0xFF val opacity = Color.alpha(color) * 100 / 0xFF
opacitySeekBar.setProgress(opacity, false) opacitySeekBar.setProgress(opacity, false)
@ -122,8 +123,8 @@ class WidgetConfigActivity : AppCompatActivity() {
private fun confirmCreateWidget() { private fun confirmCreateWidget() {
val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress) val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE) // val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
val editor = prefs.edit() val editor = prefs!!.edit()
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor) editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor)
editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.isChecked) editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked) editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked)

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
import ac.mdiq.podcini.ui.utils.ThemeUtils import ac.mdiq.podcini.ui.utils.ThemeUtils
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Logd
import android.R.color import android.R.color
import android.app.Activity import android.app.Activity
import android.util.Log import android.util.Log
@ -18,9 +19,10 @@ import java.lang.ref.WeakReference
/** /**
* List adapter for the list of new episodes. * List adapter for the list of new episodes.
*/ */
open class EpisodeItemListAdapter(mainActivity: MainActivity) : open class EpisodeItemListAdapter(mainActivity: MainActivity)
SelectableAdapter<EpisodeItemViewHolder?>(mainActivity), View.OnCreateContextMenuListener { : SelectableAdapter<EpisodeItemViewHolder?>(mainActivity), View.OnCreateContextMenuListener {
val TAG = "EpisodeItemListAdapter"
val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity) val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private var episodes: List<FeedItem> = ArrayList() private var episodes: List<FeedItem> = ArrayList()
@ -49,6 +51,8 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
} }
@UnstableApi override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeItemViewHolder { @UnstableApi override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EpisodeItemViewHolder {
// TODO: the Invalid resource ID 0x00000000 on Android 14 occurs after this and before onBindViewHolder,
// somehow, only on the first time EpisodeItemListAdapter is called
return EpisodeItemViewHolder(mainActivityRef.get()!!, parent) return EpisodeItemViewHolder(mainActivityRef.get()!!, parent)
} }
@ -79,24 +83,13 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
} }
holder.infoCard.setOnClickListener { holder.infoCard.setOnClickListener {
val activity: MainActivity? = mainActivityRef.get() val activity: MainActivity? = mainActivityRef.get()
if (!inActionMode()) { if (!inActionMode()) activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
// val ids: LongArray = FeedItemUtil.getIds(episodes) else toggleSelection(holder.bindingAdapterPosition)
// val position = ArrayUtils.indexOf(ids, item.id)
activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
} else {
toggleSelection(holder.bindingAdapterPosition)
}
} }
holder.coverHolder.setOnClickListener { holder.coverHolder.setOnClickListener {
val activity: MainActivity? = mainActivityRef.get() val activity: MainActivity? = mainActivityRef.get()
if (!inActionMode()) { if (!inActionMode()) activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
// val ids: LongArray = FeedItemUtil.getIds(episodes) else toggleSelection(holder.bindingAdapterPosition)
// val position = ArrayUtils.indexOf(ids, item.id)
activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
} else {
toggleSelection(holder.bindingAdapterPosition)
}
} }
holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent -> holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent ->
if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) { if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
@ -106,12 +99,11 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
} }
false false
}) })
if (inActionMode()) { if (inActionMode()) {
holder.secondaryActionButton.setOnClickListener(null) holder.secondaryActionButton.setOnClickListener(null)
if (isSelected(pos)) { if (isSelected(pos))
holder.itemView.setBackgroundColor(-0x78000000 + (0xffffff and ThemeUtils.getColorFromAttr(mainActivityRef.get()!!, R.attr.colorAccent))) holder.itemView.setBackgroundColor(-0x78000000 + (0xffffff and ThemeUtils.getColorFromAttr(mainActivityRef.get()!!, R.attr.colorAccent)))
} else holder.itemView.setBackgroundResource(color.transparent) else holder.itemView.setBackgroundResource(color.transparent)
} }
afterBindViewHolder(holder, pos) afterBindViewHolder(holder, pos)

View File

@ -4,13 +4,14 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.NavListitemBinding import ac.mdiq.podcini.databinding.NavListitemBinding
import ac.mdiq.podcini.databinding.NavSectionItemBinding import ac.mdiq.podcini.databinding.NavSectionItemBinding
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podcini.storage.DBWriter.ioScope
import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem
import ac.mdiq.podcini.ui.activity.PreferenceActivity import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.util.Logd
import android.app.Activity import android.app.Activity
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
@ -28,6 +29,9 @@ import androidx.media3.common.util.UnstableApi
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.apache.commons.lang3.ArrayUtils import org.apache.commons.lang3.ArrayUtils
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.text.NumberFormat import java.text.NumberFormat
@ -49,9 +53,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
init { init {
loadItems() loadItems()
appPrefs.registerOnSharedPreferenceChangeListener(this@NavListAdapter)
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.registerOnSharedPreferenceChangeListener(this)
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
@ -78,7 +80,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
QueueFragment.TAG -> R.drawable.ic_playlist_play QueueFragment.TAG -> R.drawable.ic_playlist_play
AllEpisodesFragment.TAG -> R.drawable.ic_feed AllEpisodesFragment.TAG -> R.drawable.ic_feed
DownloadsFragment.TAG -> R.drawable.ic_download DownloadsFragment.TAG -> R.drawable.ic_download
PlaybackHistoryFragment.TAG -> R.drawable.ic_history HistoryFragment.TAG -> R.drawable.ic_history
SubscriptionFragment.TAG -> R.drawable.ic_subscriptions SubscriptionFragment.TAG -> R.drawable.ic_subscriptions
StatisticsFragment.TAG -> R.drawable.ic_chart_box StatisticsFragment.TAG -> R.drawable.ic_chart_box
AddFeedFragment.TAG -> R.drawable.ic_add AddFeedFragment.TAG -> R.drawable.ic_add
@ -200,7 +202,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
} }
} }
Logd("NavListAdapter", "bindNavView getting drawable for: ${fragmentTags[position]}") // Logd("NavListAdapter", "bindNavView getting drawable for: ${fragmentTags[position]}")
holder.image.setImageResource(getDrawable(fragmentTags[position])) holder.image.setImageResource(getDrawable(fragmentTags[position]))
} }

View File

@ -11,8 +11,7 @@ import android.util.Log
/** /**
* Used by Recyclerviews that need to provide ability to select items. * Used by Recyclerviews that need to provide ability to select items.
*/ */
abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) : abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) : RecyclerView.Adapter<T>() {
RecyclerView.Adapter<T>() {
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private val selectedIds = HashSet<Long>() private val selectedIds = HashSet<Long>()

View File

@ -6,10 +6,10 @@ import ac.mdiq.podcini.storage.NavDrawerData
import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.Feed
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment
import ac.mdiq.podcini.ui.utils.CoverLoader
import android.content.Context import android.content.Context
import android.graphics.Rect import android.graphics.Rect
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Build
import android.view.* import android.view.*
import android.widget.* import android.widget.*
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
@ -153,6 +153,7 @@ open class SubscriptionsAdapter(mainActivity: MainActivity)
} }
inner class SubscriptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { inner class SubscriptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = SubscriptionItemBinding.bind(itemView) val binding = SubscriptionItemBinding.bind(itemView)
private val title = binding.titleLabel private val title = binding.titleLabel
private val producer = binding.producerLabel private val producer = binding.producerLabel

View File

@ -12,6 +12,7 @@ import ac.mdiq.podcini.net.download.FeedUpdateManager.runOnce
import ac.mdiq.podcini.databinding.EditTextDialogBinding import ac.mdiq.podcini.databinding.EditTextDialogBinding
import ac.mdiq.podcini.storage.model.feed.Feed import ac.mdiq.podcini.storage.model.feed.Feed
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
@ -37,7 +38,7 @@ import java.util.concurrent.ExecutionException
@UnstableApi private fun onConfirmed(original: String, updated: String) { @UnstableApi private fun onConfirmed(original: String, updated: String) {
try { try {
DBWriter.updateFeedDownloadURL(original, updated).get() runBlocking { DBWriter.updateFeedDownloadURL(original, updated).join() }
feed.download_url = updated feed.download_url = updated
runOnce(activityRef.get()!!, feed) runOnce(activityRef.get()!!, feed)
} catch (e: ExecutionException) { } catch (e: ExecutionException) {

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.EpisodeFilterDialogBinding import ac.mdiq.podcini.databinding.EpisodeFilterDialogBinding
import ac.mdiq.podcini.storage.model.feed.FeedFilter import ac.mdiq.podcini.storage.model.feed.FeedFilter
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration import ac.mdiq.podcini.ui.utils.ItemOffsetDecoration
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.view.LayoutInflater import android.view.LayoutInflater

View File

@ -10,10 +10,8 @@ import android.content.DialogInterface
import android.util.Log import android.util.Log
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers import java.lang.Runnable
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
object RemoveFeedDialog { object RemoveFeedDialog {
private const val TAG = "RemoveFeedDialog" private const val TAG = "RemoveFeedDialog"
@ -63,7 +61,7 @@ object RemoveFeedDialog {
try { try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
for (feed in feeds) { for (feed in feeds) {
DBWriter.deleteFeed(context, feed.id).get() runBlocking { DBWriter.deleteFeed(context, feed.id).join() }
} }
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -17,10 +17,13 @@ import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.getPrefsWithDefaults import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.getPrefsWithDefaults
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.getSharedPrefs
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.isSwipeActionEnabled import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.isSwipeActionEnabled
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
class SwipeActionsDialog(private val context: Context, private val tag: String) { @OptIn(UnstableApi::class) class SwipeActionsDialog(private val context: Context, private val tag: String) {
private lateinit var keys: List<SwipeAction> private lateinit var keys: List<SwipeAction>
private var rightAction: SwipeAction? = null private var rightAction: SwipeAction? = null
@ -55,15 +58,13 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
keys = Stream.of(keys).filter { a: SwipeAction -> keys = Stream.of(keys).filter { a: SwipeAction ->
(!a.getId().equals(SwipeAction.ADD_TO_QUEUE) && !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)) }.toList() (!a.getId().equals(SwipeAction.ADD_TO_QUEUE) && !a.getId().equals(SwipeAction.REMOVE_FROM_HISTORY)) }.toList()
} }
PlaybackHistoryFragment.TAG -> { HistoryFragment.TAG -> {
forFragment = context.getString(R.string.playback_history_label) forFragment = context.getString(R.string.playback_history_label)
keys = Stream.of(keys).toList() keys = Stream.of(keys).toList()
} }
else -> {} else -> {}
} }
if (tag != QueueFragment.TAG) { if (tag != QueueFragment.TAG) keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_QUEUE) }.toList()
keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_QUEUE) }.toList()
}
builder.setTitle(context.getString(R.string.swipeactions_label) + " - " + forFragment) builder.setTitle(context.getString(R.string.swipeactions_label) + " - " + forFragment)
val binding = SwipeactionsDialogBinding.inflate(LayoutInflater.from(context)) val binding = SwipeactionsDialogBinding.inflate(LayoutInflater.from(context))
@ -127,11 +128,9 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
if ((direction == LEFT && leftAction === action) || (direction == RIGHT && rightAction === action)) { if ((direction == LEFT && leftAction === action) || (direction == RIGHT && rightAction === action)) {
icon.setTint(getColorFromAttr(context, action.getActionColor())) icon.setTint(getColorFromAttr(context, action.getActionColor()))
item.swipeActionLabel.setTextColor(getColorFromAttr(context, action.getActionColor())) item.swipeActionLabel.setTextColor(getColorFromAttr(context, action.getActionColor()))
} else { } else icon.setTint(getColorFromAttr(context, R.attr.action_icon_color))
icon.setTint(getColorFromAttr(context, R.attr.action_icon_color))
}
item.swipeIcon.setImageDrawable(icon)
item.swipeIcon.setImageDrawable(icon)
item.root.setOnClickListener { item.root.setOnClickListener {
if (direction == LEFT) leftAction = keys[i] if (direction == LEFT) leftAction = keys[i]
else rightAction = keys[i] else rightAction = keys[i]
@ -158,13 +157,13 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
} }
private fun savePrefs(tag: String, right: String?, left: String?) { private fun savePrefs(tag: String, right: String?, left: String?) {
val prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE) getSharedPrefs(context)
prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + tag, "$right,$left").apply() SwipeActions.prefs!!.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + tag, "$right,$left").apply()
} }
private fun saveActionsEnabledPrefs(enabled: Boolean) { private fun saveActionsEnabledPrefs(enabled: Boolean) {
val prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE) getSharedPrefs(context)
prefs.edit().putBoolean(SwipeActions.KEY_PREFIX_NO_ACTION + tag, enabled).apply() SwipeActions.prefs!!.edit().putBoolean(SwipeActions.KEY_PREFIX_NO_ACTION + tag, enabled).apply()
} }
interface Callback { interface Callback {

View File

@ -6,7 +6,7 @@ import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedPreferences import ac.mdiq.podcini.storage.model.feed.FeedPreferences
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration import ac.mdiq.podcini.ui.utils.ItemOffsetDecoration
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.app.Dialog import android.app.Dialog

View File

@ -8,7 +8,7 @@ import ac.mdiq.podcini.playback.PlaybackController.Companion.setPlaybackSpeed
import ac.mdiq.podcini.playback.PlaybackController.Companion.setSkipSilence import ac.mdiq.podcini.playback.PlaybackController.Companion.setSkipSilence
import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence
import ac.mdiq.podcini.preferences.UserPreferences.playbackSpeedArray import ac.mdiq.podcini.preferences.UserPreferences.playbackSpeedArray
import ac.mdiq.podcini.ui.view.ItemOffsetDecoration import ac.mdiq.podcini.ui.utils.ItemOffsetDecoration
import ac.mdiq.podcini.ui.view.PlaybackSpeedSeekBar import ac.mdiq.podcini.ui.view.PlaybackSpeedSeekBar
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow

View File

@ -1,6 +1,8 @@
package ac.mdiq.podcini.ui.fragment package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.preferences.UserPreferences.allEpisodesSortOrder import ac.mdiq.podcini.preferences.UserPreferences.allEpisodesSortOrder
import ac.mdiq.podcini.preferences.UserPreferences.prefFilterAllEpisodes import ac.mdiq.podcini.preferences.UserPreferences.prefFilterAllEpisodes
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
@ -99,6 +101,7 @@ import org.apache.commons.lang3.StringUtils
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.AllEpisodesFilterChangedEvent -> onFilterChanged(event) is FlowEvent.AllEpisodesFilterChangedEvent -> onFilterChanged(event)
else -> {} else -> {}

View File

@ -14,6 +14,7 @@ import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.cast.CastEnabledActivity import ac.mdiq.podcini.playback.cast.CastEnabledActivity
import ac.mdiq.podcini.playback.service.PlaybackService import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
import ac.mdiq.podcini.receiver.MediaButtonReceiver import ac.mdiq.podcini.receiver.MediaButtonReceiver
@ -309,9 +310,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
Logd(TAG, "subscribing PositionFlowEvent")
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
// Logd(TAG, "PositionFlowEvent: ${event}") Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlaybackServiceEvent -> is FlowEvent.PlaybackServiceEvent ->
if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN)
@ -546,7 +546,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
// Logd(TAG, "PositionFlowEvent: ${event}") Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate(event) is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate(event)
is FlowEvent.SpeedChangedEvent -> updatePlaybackSpeedButton(event) is FlowEvent.SpeedChangedEvent -> updatePlaybackSpeedButton(event)

View File

@ -14,9 +14,9 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -104,6 +104,7 @@ import kotlinx.coroutines.withContext
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView) swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView)
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(getFilter()) swipeActions.setFilter(getFilter())
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener { binding.leftActionIcon.setOnClickListener {
@ -423,6 +424,7 @@ import kotlinx.coroutines.withContext
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale() is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> loadItems() is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> loadItems()

View File

@ -5,6 +5,8 @@ import ac.mdiq.podcini.databinding.SimpleListFragmentBinding
import ac.mdiq.podcini.playback.PlaybackController import ac.mdiq.podcini.playback.PlaybackController
import ac.mdiq.podcini.playback.base.MediaPlayerBase import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.adapter.ChaptersListAdapter import ac.mdiq.podcini.ui.adapter.ChaptersListAdapter
@ -124,6 +126,7 @@ class ChaptersFragment : AppCompatDialogFragment() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event) is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
else -> {} else -> {}

View File

@ -5,6 +5,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FragmentItunesSearchBinding import ac.mdiq.podcini.databinding.FragmentItunesSearchBinding
import ac.mdiq.podcini.databinding.SelectCountryDialogBinding import ac.mdiq.podcini.databinding.SelectCountryDialogBinding
import ac.mdiq.podcini.net.discovery.ItunesTopListLoader import ac.mdiq.podcini.net.discovery.ItunesTopListLoader
import ac.mdiq.podcini.net.discovery.ItunesTopListLoader.Companion.prefs
import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.net.discovery.PodcastSearchResult
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
@ -42,7 +43,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FragmentItunesSearchBinding? = null private var _binding: FragmentItunesSearchBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private lateinit var prefs: SharedPreferences // private lateinit var prefs: SharedPreferences
private lateinit var gridView: GridView private lateinit var gridView: GridView
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
private lateinit var txtvError: TextView private lateinit var txtvError: TextView
@ -91,10 +92,10 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
prefs = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE) // prefs = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
countryCode = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country) countryCode = prefs!!.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)
hidden = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false) hidden = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)
needsConfirm = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true) needsConfirm = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)
} }
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -167,7 +168,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
butRetry.visibility = View.VISIBLE butRetry.visibility = View.VISIBLE
butRetry.setText(R.string.discover_confirm) butRetry.setText(R.string.discover_confirm)
butRetry.setOnClickListener { butRetry.setOnClickListener {
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply() prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
needsConfirm = false needsConfirm = false
loadToplist(country) loadToplist(country)
} }
@ -225,7 +226,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
R.id.discover_hide_item -> { R.id.discover_hide_item -> {
item.setChecked(!item.isChecked) item.setChecked(!item.isChecked)
hidden = item.isChecked hidden = item.isChecked
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply() prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent()) EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent())
loadToplist(countryCode) loadToplist(countryCode)
@ -278,8 +279,8 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
hidden = false hidden = false
} }
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply() prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply() prefs!!.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent()) EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent())
loadToplist(countryCode) loadToplist(countryCode)

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.download.DownloadResult import ac.mdiq.podcini.storage.model.download.DownloadResult
import ac.mdiq.podcini.ui.adapter.DownloadLogAdapter import ac.mdiq.podcini.ui.adapter.DownloadLogAdapter
import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog import ac.mdiq.podcini.ui.dialog.DownloadLogDetailsDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
@ -83,6 +83,7 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.DownloadLogEvent -> loadDownloadLog() is FlowEvent.DownloadLogEvent -> loadDownloadLog()
else -> {} else -> {}

View File

@ -19,9 +19,9 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ItemSortDialog import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -97,6 +97,7 @@ import java.util.*
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = SwipeActions(this, TAG).attachTo(recyclerView) swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED)) swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener { binding.leftActionIcon.setOnClickListener {
@ -213,6 +214,7 @@ import java.util.*
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.FeedItemEvent -> onEventMainThread(event) is FlowEvent.FeedItemEvent -> onEventMainThread(event)
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event) is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)

View File

@ -66,7 +66,7 @@ import kotlin.math.max
private var homeFragment: EpisodeHomeFragment? = null private var homeFragment: EpisodeHomeFragment? = null
private var itemsLoaded = false private var itemLoaded = false
private var item: FeedItem? = null private var item: FeedItem? = null
private var webviewData: String? = null private var webviewData: String? = null
@ -89,16 +89,8 @@ import kotlin.math.max
private var actionButton1: ItemActionButton? = null private var actionButton1: ItemActionButton? = null
private var actionButton2: ItemActionButton? = null private var actionButton2: ItemActionButton? = null
// private var disposable: Disposable? = null
private var controller: PlaybackController? = null private var controller: PlaybackController? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// item = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) requireArguments().getSerializable(ARG_FEEDITEM, FeedItem::class.java)
// else requireArguments().getSerializable(ARG_FEEDITEM) as? FeedItem
}
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
@ -140,9 +132,7 @@ import kotlin.math.max
if (!item?.link.isNullOrEmpty()) { if (!item?.link.isNullOrEmpty()) {
homeFragment = EpisodeHomeFragment.newInstance(item!!) homeFragment = EpisodeHomeFragment.newInstance(item!!)
(activity as MainActivity).loadChildFragment(homeFragment!!) (activity as MainActivity).loadChildFragment(homeFragment!!)
} else { } else Toast.makeText(context, "Episode link is not valid ${item?.link}", Toast.LENGTH_LONG).show()
Toast.makeText(context, "Episode link is not valid ${item?.link}", Toast.LENGTH_LONG).show()
}
} }
butAction1.setOnClickListener(View.OnClickListener { butAction1.setOnClickListener(View.OnClickListener {
@ -169,9 +159,7 @@ import kotlin.math.max
}) })
controller = object : PlaybackController(requireActivity()) { controller = object : PlaybackController(requireActivity()) {
override fun loadMediaInfo() { override fun loadMediaInfo() {}
// Do nothing
}
} }
controller?.init() controller?.init()
load() load()
@ -246,7 +234,7 @@ import kotlin.math.max
@UnstableApi override fun onResume() { @UnstableApi override fun onResume() {
super.onResume() super.onResume()
if (itemsLoaded) { if (itemLoaded) {
progbarLoading.visibility = View.GONE progbarLoading.visibility = View.GONE
updateAppearance() updateAppearance()
} }
@ -258,7 +246,6 @@ import kotlin.math.max
_binding = null _binding = null
controller?.release() controller?.release()
// disposable?.dispose()
root.removeView(webvDescription) root.removeView(webvDescription)
webvDescription.clearHistory() webvDescription.clearHistory()
webvDescription.clearCache(true) webvDescription.clearCache(true)
@ -267,9 +254,9 @@ import kotlin.math.max
} }
@UnstableApi private fun onFragmentLoaded() { @UnstableApi private fun onFragmentLoaded() {
if (webviewData != null && !itemsLoaded) { if (webviewData != null && !itemLoaded)
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank") webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
}
// if (item?.link != null) binding.webView.loadUrl(item!!.link!!) // if (item?.link != null) binding.webView.loadUrl(item!!.link!!)
updateAppearance() updateAppearance()
} }
@ -279,14 +266,13 @@ import kotlin.math.max
Logd(TAG, "updateAppearance item is null") Logd(TAG, "updateAppearance item is null")
return return
} }
if (item!!.hasMedia()) { if (item!!.hasMedia()) FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.open_podcast)
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.open_podcast) // these are already available via button1 and button2
} else { else FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.open_podcast, R.id.mark_read_item, R.id.visit_website_item)
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.open_podcast, R.id.mark_read_item, R.id.visit_website_item)
}
if (item!!.feed != null) txtvPodcast.text = item!!.feed!!.title if (item!!.feed != null) txtvPodcast.text = item!!.feed!!.title
txtvTitle.text = item!!.title txtvTitle.text = item!!.title
binding.itemLink.text = item!!.link
if (item?.pubDate != null) { if (item?.pubDate != null) {
val pubDateStr = DateFormatter.formatAbbrev(context, item!!.pubDate) val pubDateStr = DateFormatter.formatAbbrev(context, item!!.pubDate)
@ -385,6 +371,7 @@ import kotlin.math.max
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.FeedItemEvent -> onEventMainThread(event) is FlowEvent.FeedItemEvent -> onEventMainThread(event)
is FlowEvent.PlayerStatusEvent -> updateButtons() is FlowEvent.PlayerStatusEvent -> updateButtons()
@ -404,7 +391,7 @@ import kotlin.math.max
} }
fun onEventMainThread(event: FlowEvent.FeedItemEvent) { fun onEventMainThread(event: FlowEvent.FeedItemEvent) {
Logd(TAG, "onEventMainThread() called with: event = [$event]") Logd(TAG, "onEventMainThread() called with: FeedItemEvent")
if (this.item == null) return if (this.item == null) return
for (item in event.items) { for (item in event.items) {
if (this.item!!.id == item.id) { if (this.item!!.id == item.id) {
@ -417,33 +404,13 @@ import kotlin.math.max
fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) { fun onEventMainThread(event: FlowEvent.EpisodeDownloadEvent) {
if (item == null || item!!.media == null) return if (item == null || item!!.media == null) return
if (!event.urls.contains(item!!.media!!.download_url)) return if (!event.urls.contains(item!!.media!!.download_url)) return
if (itemsLoaded && activity != null) updateButtons() if (itemLoaded && activity != null) updateButtons()
} }
// @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() { @UnstableApi private fun load() {
if (!itemsLoaded) progbarLoading.visibility = View.VISIBLE if (!itemLoaded) progbarLoading.visibility = View.VISIBLE
Logd(TAG, "load() called") Logd(TAG, "load() called")
// val scope = CoroutineScope(Dispatchers.Main)
lifecycleScope.launch { lifecycleScope.launch {
try { try {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
@ -459,7 +426,7 @@ import kotlin.math.max
progbarLoading.visibility = View.GONE progbarLoading.visibility = View.GONE
item = result item = result
onFragmentLoaded() onFragmentLoaded()
itemsLoaded = true itemLoaded = true
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, Log.getStackTraceString(e)) Log.e(TAG, Log.getStackTraceString(e))

View File

@ -105,6 +105,7 @@ import kotlin.math.min
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.AllEpisodesFilterChangedEvent -> page = 1 is FlowEvent.AllEpisodesFilterChangedEvent -> page = 1
else -> {} else -> {}

View File

@ -10,7 +10,8 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.EditUrlSettingsDialog import ac.mdiq.podcini.ui.dialog.EditUrlSettingsDialog
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.statistics.feed.FeedStatisticsFragment import ac.mdiq.podcini.ui.statistics.feed.FeedStatisticsFragment
import ac.mdiq.podcini.ui.view.ToolbarIconTintManager import ac.mdiq.podcini.ui.utils.TransitionEffect
import ac.mdiq.podcini.ui.utils.ToolbarIconTintManager
import ac.mdiq.podcini.util.IntentUtils import ac.mdiq.podcini.util.IntentUtils
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.ShareUtils import ac.mdiq.podcini.util.ShareUtils
@ -45,7 +46,6 @@ import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext

View File

@ -21,7 +21,8 @@ import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.* import ac.mdiq.podcini.ui.dialog.*
import ac.mdiq.podcini.ui.utils.MoreContentListFooterUtil import ac.mdiq.podcini.ui.utils.MoreContentListFooterUtil
import ac.mdiq.podcini.ui.view.ToolbarIconTintManager import ac.mdiq.podcini.ui.utils.TransitionEffect
import ac.mdiq.podcini.ui.utils.ToolbarIconTintManager
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.* import ac.mdiq.podcini.util.*
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
@ -112,6 +113,7 @@ import java.util.concurrent.Semaphore
binding.recyclerView.adapter = adapter binding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView) swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView)
lifecycle.addObserver(swipeActions)
refreshSwipeTelltale() refreshSwipeTelltale()
binding.header.leftActionIcon.setOnClickListener { binding.header.leftActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
@ -257,7 +259,7 @@ import java.util.concurrent.Semaphore
feed!!.nextPageLink = feed!!.download_url feed!!.nextPageLink = feed!!.download_url
feed!!.pageNr = 0 feed!!.pageNr = 0
try { try {
DBWriter.resetPagedFeedPage(feed).get() runBlocking { DBWriter.resetPagedFeedPage(feed).join() }
FeedUpdateManager.runOnce(requireContext(), feed) FeedUpdateManager.runOnce(requireContext(), feed)
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
throw RuntimeException(e) throw RuntimeException(e)
@ -352,6 +354,7 @@ import java.util.concurrent.Semaphore
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.QueueEvent -> loadItems() is FlowEvent.QueueEvent -> loadItems()
is FlowEvent.FavoritesEvent -> loadItems() is FlowEvent.FavoritesEvent -> loadItems()

View File

@ -286,7 +286,7 @@ class FeedSettingsFragment : Fragment() {
val setPreferencesFuture = DBWriter.persistFeedPreferences(feedPreferences!!) val setPreferencesFuture = DBWriter.persistFeedPreferences(feedPreferences!!)
Thread({ Thread({
try { try {
setPreferencesFuture.get() runBlocking { setPreferencesFuture.join() }
} catch (e: InterruptedException) { } catch (e: InterruptedException) {
e.printStackTrace() e.printStackTrace()
} catch (e: ExecutionException) { } catch (e: ExecutionException) {

View File

@ -11,7 +11,6 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.dialog.ItemSortDialog import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.statistics.subscriptions.DatesFilterDialog import ac.mdiq.podcini.ui.statistics.subscriptions.DatesFilterDialog
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.DateFormatter import ac.mdiq.podcini.util.DateFormatter
@ -28,18 +27,18 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.* import java.util.*
@UnstableApi class PlaybackHistoryFragment : BaseEpisodesListFragment() { @UnstableApi class HistoryFragment : BaseEpisodesListFragment() {
private var sortOrder : SortOrder = SortOrder.PLAYED_DATE_NEW_OLD private var sortOrder : SortOrder = SortOrder.PLAYED_DATE_NEW_OLD
private var startDate : Long = 0L private var startDate : Long = 0L
private var endDate : Long = Date().time private var endDate : Long = Date().time
override fun getFragmentTag(): String { override fun getFragmentTag(): String {
return "PlaybackHistoryFragment" return TAG
} }
override fun getPrefName(): String { override fun getPrefName(): String {
return "PlaybackHistoryFragment" return TAG
} }
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -69,7 +68,7 @@ import java.util.*
} }
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo) super.onCreateContextMenu(menu, v, menuInfo)
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> this@PlaybackHistoryFragment.onContextItemSelected(item) } MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> this@HistoryFragment.onContextItemSelected(item) }
} }
} }
listAdapter.setOnSelectModeListener(this) listAdapter.setOnSelectModeListener(this)
@ -131,6 +130,7 @@ import java.util.*
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.HistoryEvent -> { is FlowEvent.HistoryEvent -> {
sortOrder = event.sortOrder sortOrder = event.sortOrder
@ -177,6 +177,6 @@ import java.util.*
} }
companion object { companion object {
const val TAG: String = "PlaybackHistoryFragment" const val TAG: String = "HistoryFragment"
} }
} }

View File

@ -82,9 +82,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
insets insets
} }
val preferences: SharedPreferences = requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) // val preferences: SharedPreferences = requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
// TODO: what is this? // TODO: what is this?
openFolders = HashSet(preferences.getStringSet(PREF_OPEN_FOLDERS, HashSet<String>())!!) // Must not modify openFolders = HashSet(prefs!!.getStringSet(PREF_OPEN_FOLDERS, HashSet<String>())!!) // Must not modify
val navList = binding.navRecycler val navList = binding.navRecycler
navAdapter = NavListAdapter(itemAccess, requireActivity()) navAdapter = NavListAdapter(itemAccess, requireActivity())
@ -96,7 +96,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
startActivity(Intent(activity, PreferenceActivity::class.java)) startActivity(Intent(activity, PreferenceActivity::class.java))
} }
preferences.registerOnSharedPreferenceChangeListener(this) prefs!!.registerOnSharedPreferenceChangeListener(this)
return binding.root return binding.root
} }
@ -125,12 +125,13 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
_binding = null _binding = null
// scope.cancel() // scope.cancel()
requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this) prefs!!.unregisterOnSharedPreferenceChangeListener(this)
} }
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.FeedListUpdateEvent -> loadData() is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.FeedListUpdateEvent -> loadData()
is FlowEvent.QueueEvent -> onQueueChanged(event) is FlowEvent.QueueEvent -> onQueueChanged(event)
@ -292,6 +293,10 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
@VisibleForTesting @VisibleForTesting
const val PREF_NAME: String = "NavDrawerPrefs" const val PREF_NAME: String = "NavDrawerPrefs"
const val TAG: String = "NavDrawerFragment" const val TAG: String = "NavDrawerFragment"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
}
// caution: an array in re/values/arrays.xml relates to this // caution: an array in re/values/arrays.xml relates to this
@JvmField @JvmField
@ -301,15 +306,15 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
QueueFragment.TAG, QueueFragment.TAG,
AllEpisodesFragment.TAG, AllEpisodesFragment.TAG,
DownloadsFragment.TAG, DownloadsFragment.TAG,
PlaybackHistoryFragment.TAG, HistoryFragment.TAG,
StatisticsFragment.TAG, StatisticsFragment.TAG,
AddFeedFragment.TAG, AddFeedFragment.TAG,
) )
fun saveLastNavFragment(context: Context, tag: String?) { fun saveLastNavFragment(context: Context, tag: String?) {
Logd(TAG, "saveLastNavFragment(tag: $tag)") Logd(TAG, "saveLastNavFragment(tag: $tag)")
val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) // val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val edit: SharedPreferences.Editor = prefs.edit() val edit: SharedPreferences.Editor = prefs!!.edit()
if (tag != null) edit.putString(PREF_LAST_FRAGMENT_TAG, tag) if (tag != null) edit.putString(PREF_LAST_FRAGMENT_TAG, tag)
else edit.remove(PREF_LAST_FRAGMENT_TAG) else edit.remove(PREF_LAST_FRAGMENT_TAG)
@ -317,9 +322,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
} }
fun getLastNavFragment(context: Context): String { fun getLastNavFragment(context: Context): String {
val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE) // val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val lastFragment: String = prefs.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionFragment.TAG)?:"" val lastFragment: String = prefs!!.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionFragment.TAG)?:""
Logd(TAG, "getLastNavFragment() -> $lastFragment") // Logd(TAG, "getLastNavFragment() -> $lastFragment")
return lastFragment return lastFragment
} }
} }

View File

@ -14,7 +14,10 @@ import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.create
import ac.mdiq.podcini.net.download.service.Downloader import ac.mdiq.podcini.net.download.service.Downloader
import ac.mdiq.podcini.net.download.service.HttpDownloader import ac.mdiq.podcini.net.download.service.HttpDownloader
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.PREFS_NAME
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBTasks import ac.mdiq.podcini.storage.DBTasks
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
@ -35,6 +38,7 @@ import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.Context.MODE_PRIVATE import android.content.Context.MODE_PRIVATE
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences
import android.graphics.LightingColorFilter import android.graphics.LightingColorFilter
import android.os.Bundle import android.os.Bundle
import android.text.Spannable import android.text.Spannable
@ -330,6 +334,7 @@ import kotlin.concurrent.Volatile
@OptIn(UnstableApi::class) private fun procFlowEvents() { @OptIn(UnstableApi::class) private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event) is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
else -> {} else -> {}
@ -507,8 +512,8 @@ import kotlin.concurrent.Volatile
} }
if (isEnableAutodownload) { if (isEnableAutodownload) {
val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE) // val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
binding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true) binding.autoDownloadCheckBox.isChecked = prefs!!.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
} }
if (alternateFeedUrls.isEmpty()) { if (alternateFeedUrls.isEmpty()) {
@ -587,8 +592,8 @@ import kotlin.concurrent.Volatile
val autoDownload = binding.autoDownloadCheckBox.isChecked val autoDownload = binding.autoDownloadCheckBox.isChecked
feedPreferences.autoDownload = autoDownload feedPreferences.autoDownload = autoDownload
val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE) // val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
val editor = preferences.edit() val editor = prefs!!.edit()
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload) editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload)
editor.apply() editor.apply()
} }
@ -738,6 +743,12 @@ import kotlin.concurrent.Volatile
private const val PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload" private const val PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload"
private const val KEY_UP_ARROW = "up_arrow" private const val KEY_UP_ARROW = "up_arrow"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
}
@JvmStatic @JvmStatic
fun newInstance(feedUrl: String): OnlineFeedViewFragment { fun newInstance(feedUrl: String): OnlineFeedViewFragment {
val fragment = OnlineFeedViewFragment() val fragment = OnlineFeedViewFragment()

View File

@ -4,6 +4,8 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.PlayerDetailsFragmentBinding import ac.mdiq.podcini.databinding.PlayerDetailsFragmentBinding
import ac.mdiq.podcini.feed.util.ImageResourceUtils import ac.mdiq.podcini.feed.util.ImageResourceUtils
import ac.mdiq.podcini.playback.PlaybackController import ac.mdiq.podcini.playback.PlaybackController
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter.persistFeedItem import ac.mdiq.podcini.storage.DBWriter.persistFeedItem
import ac.mdiq.podcini.storage.model.feed.Chapter import ac.mdiq.podcini.storage.model.feed.Chapter
@ -11,6 +13,8 @@ import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeActions.Companion.SWIPE_ACTIONS_PREF_NAME
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.view.ShownotesWebView import ac.mdiq.podcini.ui.view.ShownotesWebView
@ -25,9 +29,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet import android.animation.AnimatorSet
import android.animation.ObjectAnimator import android.animation.ObjectAnimator
import android.app.Activity import android.app.Activity
import android.content.ClipData import android.content.*
import android.content.ClipboardManager
import android.content.Intent
import android.graphics.ColorFilter import android.graphics.ColorFilter
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -409,8 +411,8 @@ class PlayerDetailsFragment : Fragment() {
@UnstableApi private fun savePreference() { @UnstableApi private fun savePreference() {
Logd(TAG, "Saving preferences") Logd(TAG, "Saving preferences")
val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE) // val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val editor = prefs.edit() val editor = prefs!!.edit()
if (controller?.getMedia() != null) { if (controller?.getMedia() != null) {
Logd(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY) Logd(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY)
editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY) editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY)
@ -429,9 +431,9 @@ class PlayerDetailsFragment : Fragment() {
Logd(TAG, "Restoring from preferences") Logd(TAG, "Restoring from preferences")
val activity: Activity? = activity val activity: Activity? = activity
if (activity != null) { if (activity != null) {
val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE) // val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val id = prefs.getString(PREF_PLAYABLE_ID, "") val id = prefs!!.getString(PREF_PLAYABLE_ID, "")
val scrollY = prefs.getInt(PREF_SCROLL_Y, -1) val scrollY = prefs!!.getInt(PREF_SCROLL_Y, -1)
if (scrollY != -1) { if (scrollY != -1) {
if (id == controller?.getMedia()?.getIdentifier()?.toString()) { if (id == controller?.getMedia()?.getIdentifier()?.toString()) {
Logd(TAG, "Restored scroll Position: $scrollY") Logd(TAG, "Restored scroll Position: $scrollY")
@ -455,6 +457,7 @@ class PlayerDetailsFragment : Fragment() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event) is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
else -> {} else -> {}
@ -525,5 +528,10 @@ class PlayerDetailsFragment : Fragment() {
private const val PREF = "ItemDescriptionFragmentPrefs" private const val PREF = "ItemDescriptionFragmentPrefs"
private const val PREF_SCROLL_Y = "prefScrollY" private const val PREF_SCROLL_Y = "prefScrollY"
private const val PREF_PLAYABLE_ID = "prefPlayableId" private const val PREF_PLAYABLE_ID = "prefPlayableId"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREF, Context.MODE_PRIVATE)
}
} }
} }

View File

@ -21,9 +21,9 @@ import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.dialog.ItemSortDialog import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Converter import ac.mdiq.podcini.util.Converter
import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.util.FeedItemUtil
@ -72,7 +72,6 @@ import java.util.*
private lateinit var toolbar: MaterialToolbar private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var swipeActions: SwipeActions private lateinit var swipeActions: SwipeActions
private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView private lateinit var speedDialView: SpeedDialView
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
@ -88,7 +87,7 @@ import java.util.*
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
retainInstance = true retainInstance = true
prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE) // ioScope.launch { prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE) }
} }
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -122,15 +121,12 @@ import java.util.*
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = QueueSwipeActions() swipeActions = QueueSwipeActions()
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED)) swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions.attachTo(recyclerView) swipeActions.attachTo(recyclerView)
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener { binding.leftActionIcon.setOnClickListener { swipeActions.showDialog() }
swipeActions.showDialog() binding.rightActionIcon.setOnClickListener { swipeActions.showDialog() }
}
binding.rightActionIcon.setOnClickListener {
swipeActions.showDialog()
}
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) { recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
@ -143,9 +139,7 @@ import java.util.*
swipeRefreshLayout = binding.swipeRefresh swipeRefreshLayout = binding.swipeRefresh
swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance)) swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout.setOnRefreshListener { swipeRefreshLayout.setOnRefreshListener { FeedUpdateManager.runOnceOrAsk(requireContext()) }
FeedUpdateManager.runOnceOrAsk(requireContext())
}
emptyView = EmptyViewHandler(requireContext()) emptyView = EmptyViewHandler(requireContext())
emptyView.attachToRecyclerView(recyclerView) emptyView.attachToRecyclerView(recyclerView)
@ -200,6 +194,7 @@ import java.util.*
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.QueueEvent -> onEventMainThread(event) is FlowEvent.QueueEvent -> onEventMainThread(event)
is FlowEvent.FeedItemEvent -> onEventMainThread(event) is FlowEvent.FeedItemEvent -> onEventMainThread(event)
@ -406,7 +401,7 @@ import java.util.*
val isLocked: Boolean = UserPreferences.isQueueLocked val isLocked: Boolean = UserPreferences.isQueueLocked
if (isLocked) setQueueLocked(false) if (isLocked) setQueueLocked(false)
else { else {
val shouldShowLockWarning: Boolean = prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true) val shouldShowLockWarning: Boolean = prefs!!.getBoolean(PREF_SHOW_LOCK_WARNING, true)
if (!shouldShowLockWarning) setQueueLocked(true) if (!shouldShowLockWarning) setQueueLocked(true)
else { else {
val builder = MaterialAlertDialogBuilder(requireContext()) val builder = MaterialAlertDialogBuilder(requireContext())
@ -419,7 +414,7 @@ import java.util.*
builder.setView(view) builder.setView(view)
builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int -> builder.setPositiveButton(R.string.lock_queue) { _: DialogInterface?, _: Int ->
prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply() prefs!!.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply()
setQueueLocked(true) setQueueLocked(true)
} }
builder.setNegativeButton(R.string.cancel_label, null) builder.setNegativeButton(R.string.cancel_label, null)
@ -586,7 +581,6 @@ import java.util.*
// Update tracked position // Update tracked position
if (dragFrom == -1) dragFrom = fromPosition if (dragFrom == -1) dragFrom = fromPosition
dragTo = toPosition dragTo = toPosition
val from = viewHolder.bindingAdapterPosition val from = viewHolder.bindingAdapterPosition
@ -632,5 +626,11 @@ import java.util.*
private const val PREFS = "QueueFragment" private const val PREFS = "QueueFragment"
private const val PREF_SHOW_LOCK_WARNING = "show_lock_warning" private const val PREF_SHOW_LOCK_WARNING = "show_lock_warning"
private var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
}
} }
} }

View File

@ -4,6 +4,7 @@ import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.QuickFeedDiscoveryBinding import ac.mdiq.podcini.databinding.QuickFeedDiscoveryBinding
import ac.mdiq.podcini.net.discovery.ItunesTopListLoader import ac.mdiq.podcini.net.discovery.ItunesTopListLoader
import ac.mdiq.podcini.net.discovery.ItunesTopListLoader.Companion.prefs
import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.net.discovery.PodcastSearchResult
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
@ -11,8 +12,6 @@ import ac.mdiq.podcini.ui.adapter.FeedDiscoverAdapter
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log import android.util.Log
@ -96,6 +95,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.DiscoveryDefaultUpdateEvent -> loadToplist() is FlowEvent.DiscoveryDefaultUpdateEvent -> loadToplist()
else -> {} else -> {}
@ -111,9 +111,9 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
poweredByTextView.visibility = View.VISIBLE poweredByTextView.visibility = View.VISIBLE
val loader = ItunesTopListLoader(requireContext()) val loader = ItunesTopListLoader(requireContext())
val prefs: SharedPreferences = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE) // val prefs: SharedPreferences = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
val countryCode: String = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)!! val countryCode: String = prefs!!.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)!!
if (prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) { if (prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) {
errorTextView.setText(R.string.discover_is_hidden) errorTextView.setText(R.string.discover_is_hidden)
errorView.visibility = View.VISIBLE errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.GONE discoverGridLayout.visibility = View.GONE
@ -121,7 +121,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
poweredByTextView.visibility = View.GONE poweredByTextView.visibility = View.GONE
return return
} }
if (BuildConfig.FLAVOR == "free" && prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)) { if (BuildConfig.FLAVOR == "free" && prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)) {
errorTextView.text = "" errorTextView.text = ""
errorView.visibility = View.VISIBLE errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.VISIBLE discoverGridLayout.visibility = View.VISIBLE
@ -129,7 +129,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
errorRetry.setText(R.string.discover_confirm) errorRetry.setText(R.string.discover_confirm)
poweredByTextView.visibility = View.VISIBLE poweredByTextView.visibility = View.VISIBLE
errorRetry.setOnClickListener { errorRetry.setOnClickListener {
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply() prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
loadToplist() loadToplist()
} }
return return
@ -185,7 +185,6 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
errorRetry.setOnClickListener { loadToplist() } errorRetry.setOnClickListener { loadToplist() }
} }
} }
} }
@OptIn(UnstableApi::class) override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) { @OptIn(UnstableApi::class) override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {

View File

@ -16,9 +16,9 @@ import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.HorizontalFeedListAdapter import ac.mdiq.podcini.ui.adapter.HorizontalFeedListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -239,6 +239,7 @@ import kotlinx.coroutines.withContext
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> search() is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> search()
is FlowEvent.FeedItemEvent -> onEventMainThread(event) is FlowEvent.FeedItemEvent -> onEventMainThread(event)

View File

@ -16,13 +16,11 @@ import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.adapter.SubscriptionsAdapter import ac.mdiq.podcini.ui.adapter.SubscriptionsAdapter
import ac.mdiq.podcini.ui.dialog.FeedSortDialog import ac.mdiq.podcini.ui.dialog.FeedSortDialog
import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.*
@ -62,7 +60,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
private lateinit var toolbar: MaterialToolbar private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
private lateinit var prefs: SharedPreferences // private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView private lateinit var speedDialView: SpeedDialView
private var tagFilterIndex = 1 private var tagFilterIndex = 1
@ -78,7 +76,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
retainInstance = true retainInstance = true
prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE) // prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE)
} }
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -236,6 +234,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event) is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
is FlowEvent.UnreadItemsUpdateEvent -> loadSubscriptions() is FlowEvent.UnreadItemsUpdateEvent -> loadSubscriptions()

View File

@ -8,6 +8,8 @@ import ac.mdiq.podcini.playback.PlaybackController.Companion.setVideoSurface
import ac.mdiq.podcini.playback.PlaybackController.Companion.videoSize import ac.mdiq.podcini.playback.PlaybackController.Companion.videoSize
import ac.mdiq.podcini.playback.base.MediaPlayerBase import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs
import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
@ -155,6 +157,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event) is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)
is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate() is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate()

View File

@ -3,6 +3,7 @@ package ac.mdiq.podcini.ui.statistics
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.PagerFragmentBinding import ac.mdiq.podcini.databinding.PagerFragmentBinding
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.PREFS_NAME
import ac.mdiq.podcini.storage.DBWriter import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
@ -14,6 +15,7 @@ import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -98,7 +100,7 @@ class StatisticsFragment : PagedToolbarFragment() {
} }
@UnstableApi private fun doResetStatistics() { @UnstableApi private fun doResetStatistics() {
requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit() prefs!!.edit()
.putBoolean(PREF_INCLUDE_MARKED_PLAYED, false) .putBoolean(PREF_INCLUDE_MARKED_PLAYED, false)
.putLong(PREF_FILTER_FROM, 0) .putLong(PREF_FILTER_FROM, 0)
.putLong(PREF_FILTER_TO, Long.MAX_VALUE) .putLong(PREF_FILTER_TO, Long.MAX_VALUE)
@ -146,10 +148,16 @@ class StatisticsFragment : PagedToolbarFragment() {
const val PREF_FILTER_FROM: String = "filterFrom" const val PREF_FILTER_FROM: String = "filterFrom"
const val PREF_FILTER_TO: String = "filterTo" const val PREF_FILTER_TO: String = "filterTo"
private const val POS_SUBSCRIPTIONS = 0 private const val POS_SUBSCRIPTIONS = 0
private const val POS_YEARS = 1 private const val POS_YEARS = 1
private const val POS_SPACE_TAKEN = 2 private const val POS_SPACE_TAKEN = 2
private const val TOTAL_COUNT = 3 private const val TOTAL_COUNT = 3
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
}
} }
} }

View File

@ -28,7 +28,6 @@ abstract class DatesFilterDialog(private val context: Context, oldestDate: Long)
protected val filterDatesFrom: Pair<Array<String>, Array<Long>> protected val filterDatesFrom: Pair<Array<String>, Array<Long>>
protected val filterDatesTo: Pair<Array<String>, Array<Long>> protected val filterDatesTo: Pair<Array<String>, Array<Long>>
init { init {
initParams() initParams()
filterDatesFrom = makeMonthlyList(oldestDate, false) filterDatesFrom = makeMonthlyList(oldestDate, false)

View File

@ -4,15 +4,13 @@ package ac.mdiq.podcini.ui.statistics.subscriptions
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.StatisticsFragmentBinding import ac.mdiq.podcini.databinding.StatisticsFragmentBinding
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem
import ac.mdiq.podcini.storage.DBReader.StatisticsResult import ac.mdiq.podcini.storage.DBReader.StatisticsResult
import ac.mdiq.podcini.storage.StatisticsItem import ac.mdiq.podcini.storage.StatisticsItem
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment.Companion.prefs
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment.Companion import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.*
@ -21,10 +19,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -77,6 +71,7 @@ class SubscriptionStatisticsFragment : Fragment() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.StatisticsEvent -> refreshStatistics() is FlowEvent.StatisticsEvent -> refreshStatistics()
else -> {} else -> {}
@ -96,7 +91,7 @@ class SubscriptionStatisticsFragment : Fragment() {
if (statisticsResult != null) { if (statisticsResult != null) {
val dialog = object: DatesFilterDialog(requireContext(), statisticsResult!!.oldestDate) { val dialog = object: DatesFilterDialog(requireContext(), statisticsResult!!.oldestDate) {
override fun initParams() { override fun initParams() {
prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE) prefs = StatisticsFragment.prefs
includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false) includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0) timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE) timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
@ -126,10 +121,10 @@ class SubscriptionStatisticsFragment : Fragment() {
private fun loadStatistics() { private fun loadStatistics() {
// disposable?.dispose() // disposable?.dispose()
val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE) // val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
val includeMarkedAsPlayed = prefs.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false) val includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
val timeFilterFrom = prefs.getLong(StatisticsFragment.PREF_FILTER_FROM, 0) val timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
val timeFilterTo = prefs.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE) val timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
// disposable = Observable.fromCallable { // disposable = Observable.fromCallable {
// val statisticsData = DBReader.getStatistics( // val statisticsData = DBReader.getStatistics(

View File

@ -3,8 +3,11 @@ package ac.mdiq.podcini.ui.statistics.years
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.StatisticsFragmentBinding import ac.mdiq.podcini.databinding.StatisticsFragmentBinding
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.os.Bundle import android.os.Bundle
@ -65,6 +68,7 @@ class YearsStatisticsFragment : Fragment() {
private fun procFlowEvents() { private fun procFlowEvents() {
lifecycleScope.launch { lifecycleScope.launch {
EventFlow.events.collectLatest { event -> EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) { when (event) {
is FlowEvent.StatisticsEvent -> refreshStatistics() is FlowEvent.StatisticsEvent -> refreshStatistics()
else -> {} else -> {}

View File

@ -1,7 +1,8 @@
package ac.mdiq.podcini.ui.adapter package ac.mdiq.podcini.ui.utils
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.util.Logd
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.widget.ImageView import android.widget.ImageView
@ -75,6 +76,7 @@ class CoverLoader(private val activity: MainActivity) {
.setHeader("User-Agent", "Mozilla/5.0") .setHeader("User-Agent", "Mozilla/5.0")
.listener(object : ImageRequest.Listener { .listener(object : ImageRequest.Listener {
override fun onError(request: ImageRequest, throwable: ErrorResult) { override fun onError(request: ImageRequest, throwable: ErrorResult) {
Logd("CoverLoader", "Trying to get fallback image")
val fallbackImageRequest = ImageRequest.Builder(activity) val fallbackImageRequest = ImageRequest.Builder(activity)
.data(fallbackUri) .data(fallbackUri)
.setHeader("User-Agent", "Mozilla/5.0") .setHeader("User-Agent", "Mozilla/5.0")
@ -86,9 +88,7 @@ class CoverLoader(private val activity: MainActivity) {
}) })
.target(coverTargetCoil) .target(coverTargetCoil)
.build() .build()
activity.imageLoader activity.imageLoader.enqueue(request)
.enqueue(request)
} }
internal class CoilCoverTarget(fallbackTitle: TextView?, coverImage: ImageView, private val textAndImageCombined: Boolean) : Target { internal class CoilCoverTarget(fallbackTitle: TextView?, coverImage: ImageView, private val textAndImageCombined: Boolean) : Target {

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import android.content.Context import android.content.Context
import android.database.DataSetObserver import android.database.DataSetObserver

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import android.content.Context import android.content.Context
import android.graphics.Rect import android.graphics.Rect

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.view.View import android.view.View

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import android.content.Context import android.content.Context

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view package ac.mdiq.podcini.ui.utils
import android.content.Context import android.content.Context
import android.graphics.PorterDuff import android.graphics.PorterDuff

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.fragment package ac.mdiq.podcini.ui.utils
enum class TransitionEffect { enum class TransitionEffect {
NONE, FADE, SLIDE NONE, FADE, SLIDE

View File

@ -8,6 +8,8 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.PREFS_NAME
import android.content.SharedPreferences
class EpisodeItemListRecyclerView : RecyclerView { class EpisodeItemListRecyclerView : RecyclerView {
private lateinit var layoutManager: LinearLayoutManager private lateinit var layoutManager: LinearLayoutManager
@ -46,16 +48,16 @@ class EpisodeItemListRecyclerView : RecyclerView {
val firstItemView = layoutManager.findViewByPosition(firstItem) val firstItemView = layoutManager.findViewByPosition(firstItem)
val topOffset = firstItemView?.top?.toFloat() ?: 0f val topOffset = firstItemView?.top?.toFloat() ?: 0f
context.getSharedPreferences(TAG, Context.MODE_PRIVATE).edit() prefs!!.edit()
.putInt(PREF_PREFIX_SCROLL_POSITION + tag, firstItem) .putInt(PREF_PREFIX_SCROLL_POSITION + tag, firstItem)
.putInt(PREF_PREFIX_SCROLL_OFFSET + tag, topOffset.toInt()) .putInt(PREF_PREFIX_SCROLL_OFFSET + tag, topOffset.toInt())
.apply() .apply()
} }
fun restoreScrollPosition(tag: String) { fun restoreScrollPosition(tag: String) {
val prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE) // val prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
val position = prefs.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0) val position = prefs!!.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0)
val offset = prefs.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0) val offset = prefs!!.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0)
if (position > 0 || offset > 0) layoutManager.scrollToPositionWithOffset(position, offset) if (position > 0 || offset > 0) layoutManager.scrollToPositionWithOffset(position, offset)
} }
@ -71,5 +73,11 @@ class EpisodeItemListRecyclerView : RecyclerView {
private const val TAG = "EpisodeItemListRecyclerView" private const val TAG = "EpisodeItemListRecyclerView"
private const val PREF_PREFIX_SCROLL_POSITION = "scroll_position_" private const val PREF_PREFIX_SCROLL_POSITION = "scroll_position_"
private const val PREF_PREFIX_SCROLL_OFFSET = "scroll_offset_" private const val PREF_PREFIX_SCROLL_OFFSET = "scroll_offset_"
var prefs: SharedPreferences? = null
fun getSharedPrefs(context: Context) {
if (prefs == null) prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
}
} }
} }

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.ui.view.viewholder package ac.mdiq.podcini.ui.view.viewholder
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import android.os.Build
import android.text.Layout import android.text.Layout
import android.text.format.Formatter import android.text.format.Formatter
import android.util.Log import android.util.Log
@ -18,7 +17,7 @@ import com.google.android.material.elevation.SurfaceColors
import com.joanzapata.iconify.Iconify import com.joanzapata.iconify.Iconify
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FeeditemlistItemBinding import ac.mdiq.podcini.databinding.FeeditemlistItemBinding
import ac.mdiq.podcini.ui.adapter.CoverLoader import ac.mdiq.podcini.ui.utils.CoverLoader
import ac.mdiq.podcini.feed.util.ImageResourceUtils import ac.mdiq.podcini.feed.util.ImageResourceUtils
import ac.mdiq.podcini.net.download.MediaSizeLoader import ac.mdiq.podcini.net.download.MediaSizeLoader
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
@ -37,7 +36,6 @@ import ac.mdiq.podcini.util.*
import ac.mdiq.podcini.util.event.FlowEvent import ac.mdiq.podcini.util.event.FlowEvent
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat.getDrawable
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
import kotlin.math.max import kotlin.math.max
@ -45,8 +43,8 @@ import kotlin.math.max
* Holds the view which shows FeedItems. * Holds the view which shows FeedItems.
*/ */
@UnstableApi @UnstableApi
open class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) : open class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup)
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)) { : RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.feeditemlist_item, parent, false)) {
val binding: FeeditemlistItemBinding = FeeditemlistItemBinding.bind(itemView) val binding: FeeditemlistItemBinding = FeeditemlistItemBinding.bind(itemView)
@ -108,7 +106,7 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
container.alpha = if (item.isPlayed()) 0.75f else 1.0f container.alpha = if (item.isPlayed()) 0.75f else 1.0f
val newButton = ItemActionButton.forItem(item) val newButton = ItemActionButton.forItem(item)
Logd(TAG, "bind ${actionButton?.TAG} ${newButton.TAG} ${item.title}") Logd(TAG, "Trying to bind button ${actionButton?.TAG} ${newButton.TAG} ${item.title}")
// not using a new button to ensure valid progress values, for TTS audio generation // not using a new button to ensure valid progress values, for TTS audio generation
if (!(actionButton?.TAG == TTSActionButton::class.simpleName && newButton.TAG == TTSActionButton::class.simpleName)) { if (!(actionButton?.TAG == TTSActionButton::class.simpleName && newButton.TAG == TTSActionButton::class.simpleName)) {
actionButton = newButton actionButton = newButton
@ -118,11 +116,9 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
// Log.d(TAG, "bind called ${item.media}") // Log.d(TAG, "bind called ${item.media}")
when { when {
item.media != null -> { item.media != null -> bind(item.media!!)
bind(item.media!!) // for generating TTS files for episode without media
}
item.playState == BUILDING -> { item.playState == BUILDING -> {
// for generating TTS files for episode without media
secondaryActionProgress.setPercentage(actionButton!!.processing, item) secondaryActionProgress.setPercentage(actionButton!!.processing, item)
secondaryActionProgress.setIndeterminate(false) secondaryActionProgress.setIndeterminate(false)
} }
@ -139,15 +135,16 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
if (coverHolder.visibility == View.VISIBLE) { if (coverHolder.visibility == View.VISIBLE) {
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(item) 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) if (!imgLoc.isNullOrBlank() && !imgLoc.contains(PREFIX_GENERATIVE_COVER))
.withUri(imgLoc) CoverLoader(activity)
.withFallbackUri(item.feed?.imageUrl) .withUri(imgLoc)
.withPlaceholderView(placeholder) .withFallbackUri(item.feed?.imageUrl)
.withCoverView(cover) .withPlaceholderView(placeholder)
.load() .withCoverView(cover)
.load()
else { else {
Logd(TAG, "setting to ic_launcher") Logd(TAG, "setting cover to ic_launcher")
cover.setImageDrawable(AppCompatResources.getDrawable(activity, R.drawable.ic_launcher_foreground)) cover.setImageDrawable(AppCompatResources.getDrawable(activity, R.drawable.ic_launcher_foreground))
} }
} }
@ -211,12 +208,11 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
NetworkUtils.isEpisodeHeadDownloadAllowed && !media.checkedOnSizeButUnknown() -> { NetworkUtils.isEpisodeHeadDownloadAllowed && !media.checkedOnSizeButUnknown() -> {
size.text = "{fa-spinner}" size.text = "{fa-spinner}"
Iconify.addIcons(size) Iconify.addIcons(size)
MediaSizeLoader.getFeedMediaSizeObservable(media).subscribe( MediaSizeLoader.getFeedMediaSizeObservable(media).subscribe(Consumer<Long?> { sizeValue: Long? ->
Consumer<Long?> { sizeValue: Long? -> if (sizeValue == null) return@Consumer
if (sizeValue == null) return@Consumer if (sizeValue > 0) size.text = Formatter.formatShortFileSize(activity, sizeValue)
if (sizeValue > 0) size.text = Formatter.formatShortFileSize(activity, sizeValue) else size.text = ""
else size.text = "" }) { error: Throwable? ->
}) { error: Throwable? ->
size.text = "" size.text = ""
Log.e(TAG, Log.getStackTraceString(error)) Log.e(TAG, Log.getStackTraceString(error))
} }

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createPendingIntent import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createPendingIntent
import ac.mdiq.podcini.receiver.PlayerWidget import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.isEnabled import ac.mdiq.podcini.receiver.PlayerWidget.Companion.isEnabled
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.MediaType
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter import ac.mdiq.podcini.ui.activity.appstartintent.MainActivityStarter
@ -14,13 +15,10 @@ import ac.mdiq.podcini.ui.activity.appstartintent.PlaybackSpeedActivityStarter
import ac.mdiq.podcini.ui.activity.appstartintent.VideoPlayerActivityStarter import ac.mdiq.podcini.ui.activity.appstartintent.VideoPlayerActivityStarter
import ac.mdiq.podcini.util.Converter.getDurationStringLong import ac.mdiq.podcini.util.Converter.getDurationStringLong
import ac.mdiq.podcini.util.TimeSpeedConverter import ac.mdiq.podcini.util.TimeSpeedConverter
import android.R.attr.bitmap
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.BitmapDrawable
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@ -165,16 +163,16 @@ object WidgetUpdater {
for (id in widgetIds) { for (id in widgetIds) {
val options = manager.getAppWidgetOptions(id) val options = manager.getAppWidgetOptions(id)
val prefs = context.getSharedPreferences(PlayerWidget.PREFS_NAME, Context.MODE_PRIVATE) // val prefs = context.getSharedPreferences(PlayerWidget.PREFS_NAME, Context.MODE_PRIVATE)
val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
val columns = getCellsForSize(minWidth) val columns = getCellsForSize(minWidth)
if (columns < 3) views.setViewVisibility(R.id.layout_center, View.INVISIBLE) if (columns < 3) views.setViewVisibility(R.id.layout_center, View.INVISIBLE)
else views.setViewVisibility(R.id.layout_center, View.VISIBLE) else views.setViewVisibility(R.id.layout_center, View.VISIBLE)
val showPlaybackSpeed = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + id, true) val showPlaybackSpeed = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + id, true)
val showRewind = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + id, true) val showRewind = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + id, true)
val showFastForward = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + id, true) val showFastForward = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + id, true)
val showSkip = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + id, true) val showSkip = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + id, true)
if (showPlaybackSpeed || showRewind || showSkip || showFastForward) { if (showPlaybackSpeed || showRewind || showSkip || showFastForward) {
views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.VISIBLE) views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.VISIBLE)
@ -188,7 +186,7 @@ object WidgetUpdater {
views.setInt(R.id.butPlay, "setVisibility", View.VISIBLE) views.setInt(R.id.butPlay, "setVisibility", View.VISIBLE)
} }
val backgroundColor = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + id, PlayerWidget.DEFAULT_COLOR) val backgroundColor = prefs!!.getInt(PlayerWidget.KEY_WIDGET_COLOR + id, PlayerWidget.DEFAULT_COLOR)
views.setInt(R.id.widgetLayout, "setBackgroundColor", backgroundColor) views.setInt(R.id.widgetLayout, "setBackgroundColor", backgroundColor)
manager.updateAppWidget(id, views) manager.updateAppWidget(id, views)

View File

@ -2,7 +2,7 @@ package ac.mdiq.podcini.ui.widget
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils.getCurrentPlaybackSpeed import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils.getCurrentPlaybackSpeed
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import android.content.Context import android.content.Context
@ -24,7 +24,7 @@ class WidgetUpdaterWorker(context: Context, workerParams: WorkerParameters) : Wo
* Loads the current media from the database and updates the widget in a background job. * Loads the current media from the database and updates the widget in a background job.
*/ */
private fun updateWidget() { private fun updateWidget() {
val media = createInstanceFromPreferences(applicationContext) val media = loadPlayableFromPreferences()
if (media != null) WidgetUpdater.updateWidget(applicationContext, WidgetState(media, PlayerStatus.STOPPED, media.getPosition(), media.getDuration(), getCurrentPlaybackSpeed(media))) if (media != null) WidgetUpdater.updateWidget(applicationContext, WidgetState(media, PlayerStatus.STOPPED, media.getPosition(), media.getDuration(), getCurrentPlaybackSpeed(media)))
else WidgetUpdater.updateWidget(applicationContext, WidgetState(PlayerStatus.STOPPED)) else WidgetUpdater.updateWidget(applicationContext, WidgetState(PlayerStatus.STOPPED))
} }

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.util package ac.mdiq.podcini.util
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.receiver.SPAReceiver import ac.mdiq.podcini.receiver.SPAReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -31,12 +32,12 @@ object SPAUtil {
Log.wtf(TAG, "Unable to get application context") Log.wtf(TAG, "Unable to get application context")
return false return false
} }
val prefs = PreferenceManager.getDefaultSharedPreferences(appContext) // val prefs = PreferenceManager.getDefaultSharedPreferences(appContext)
if (!prefs.getBoolean(PREF_HAS_QUERIED_SP_APPS, false)) { if (!appPrefs.getBoolean(PREF_HAS_QUERIED_SP_APPS, false)) {
appContext.sendBroadcast(Intent(SPAReceiver.ACTION_SP_APPS_QUERY_FEEDS)) appContext.sendBroadcast(Intent(SPAReceiver.ACTION_SP_APPS_QUERY_FEEDS))
Logd(TAG, "Sending SP_APPS_QUERY_FEEDS intent") Logd(TAG, "Sending SP_APPS_QUERY_FEEDS intent")
val editor = prefs.edit() val editor = appPrefs.edit()
editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, true) editor.putBoolean(PREF_HAS_QUERIED_SP_APPS, true)
editor.apply() editor.apply()

View File

@ -18,6 +18,9 @@ import ac.mdiq.podcini.storage.database.PodDBAdapter
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig
import ac.mdiq.podcini.net.download.service.DownloadServiceInterfaceImpl import ac.mdiq.podcini.net.download.service.DownloadServiceInterfaceImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File import java.io.File
@UnstableApi @UnstableApi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 590 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 953 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 910 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 813 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 375 KiB

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