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,
3. Is purely `Kotlin` based and mono-modular,
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,
7. Accepts podcast as well as plain RSS and YouTube feeds,
8. Offers Readability and Text-to-Speech for RSS contents,

View File

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

View File

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

View File

@ -147,12 +147,12 @@ class NavigationDrawerTest {
val hidden = hiddenDrawerItems
Assert.assertEquals(2, hidden!!.size.toLong())
Assert.assertTrue(hidden.contains(AllEpisodesFragment.TAG))
Assert.assertTrue(hidden.contains(PlaybackHistoryFragment.TAG))
Assert.assertTrue(hidden.contains(HistoryFragment.TAG))
}
@Test
fun testDrawerPreferencesUnhideSomeElements() {
var hidden = listOf(PlaybackHistoryFragment.TAG, DownloadsFragment.TAG)
var hidden = listOf(HistoryFragment.TAG, DownloadsFragment.TAG)
hiddenDrawerItems = hidden
activityRule.launchActivity(Intent())
openNavDrawer()
@ -166,7 +166,7 @@ class NavigationDrawerTest {
hidden = hiddenDrawerItems?.filterNotNull()?: listOf()
Assert.assertEquals(2, hidden.size.toLong())
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.fonts.FontAwesomeModule
import com.joanzapata.iconify.fonts.MaterialModule
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/** Main application class. */
class PodciniApp : Application() {
@ -42,9 +45,12 @@ class PodciniApp : Application() {
singleton = this
ClientConfigurator.initialize(this)
PreferenceUpgrader.checkUpgrades(this)
runBlocking {
withContext(Dispatchers.IO) {
ClientConfigurator.initialize(this@PodciniApp)
PreferenceUpgrader.checkUpgrades(this@PodciniApp)
}
}
Iconify.with(FontAwesomeModule())
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.storage.model.feed.Feed
import ac.mdiq.podcini.util.Logd
import android.content.SharedPreferences
import okhttp3.CacheControl
import okhttp3.OkHttpClient
import okhttp3.Request
@ -81,6 +82,11 @@ class ItunesTopListLoader(private val context: Context) {
const val COUNTRY_CODE_UNSET: String = "99"
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> {
val subscribedPodcastsSet: MutableSet<String> = HashSet()
for (subscribedFeed in subscribedFeeds) {

View File

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

View File

@ -16,6 +16,7 @@ import android.content.Context
import android.media.MediaMetadataRetriever
import android.util.Log
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.io.File
import java.util.concurrent.ExecutionException
@ -60,7 +61,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
val item = media.item
try {
DBWriter.persistFeedMedia(media).get()
runBlocking { DBWriter.persistFeedMedia(media).join() }
// we've received the media, we don't want to autodownload it again
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,
// so we do it after the enclosing media has been updated above,
// 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())
}
} catch (e: InterruptedException) {

View File

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

View File

@ -57,6 +57,7 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
@UnstableApi override fun doWork(): Result {
Logd(TAG, "doWork() called")
val activeSyncProvider = getActiveSyncProvider() ?: return Result.failure()
Logd(TAG, "doWork() got syn provider")
SynchronizationSettings.updateLastSynchronizationAttempt()
setCurrentlyActive(true)
@ -165,15 +166,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
return@launch
}
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> {
@ -315,11 +307,6 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont
if (selectedService == null) return null
return when (selectedService) {
// SynchronizationProviderViewData.WIFI -> {
//// if (hosturl != null) WifiImplSyncService(hosturl!!, hostport)
//// else null
// null
// }
SynchronizationProviderViewData.GPODDER_NET -> GpodnetService(getHttpClient(), hosturl, deviceID?:"", 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.PlaybackServiceConstants
import ac.mdiq.podcini.preferences.PlaybackPreferences
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.loadPlayableFromPreferences
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedMedia
@ -24,8 +25,11 @@ import android.view.SurfaceHolder
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
/**
* 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() {
activity.lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackServiceEvent -> if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_STARTED) init()
else -> {}
@ -305,9 +310,13 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
fun getMedia(): Playable? {
if (media == null && playbackService != null) media = playbackService!!.mPlayerInfo.playable
if (media == null) media = PlaybackPreferences.createInstanceFromPreferences(activity)
return media
if (media != null) return media
return runBlocking {
media = withContext(Dispatchers.IO) {
loadPlayableFromPreferences()
}
media
}
}
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.RewindAfterPauseUtils
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.playback.MediaType
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?) {
Logd(TAG, "setDataSource: $s")
val httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
.setUserAgent(ClientConfig.USER_AGENT)
if (httpDataSourceFactory == null)
httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory).setUserAgent(ClientConfig.USER_AGENT)
if (!user.isNullOrEmpty() && !password.isNullOrEmpty()) {
val requestProperties = HashMap<String, String>()
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()
extractorsFactory.setConstantBitrateSeekingEnabled(true)
extractorsFactory.setMp3ExtractorFlags(Mp3Extractor.FLAG_DISABLE_ID3_METADATA)
@ -282,6 +283,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
}
else -> {
val localMediaurl = this.playable!!.getLocalMediaUrl()
// TODO: File(localMediaurl).canRead() time consuming
if (!localMediaurl.isNullOrEmpty() && File(localMediaurl).canRead()) setDataSource(metadata, localMediaurl, null, null)
else throw IOException("Unable to read local file $localMediaurl")
}
@ -603,8 +605,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
* invalid values.
*/
override fun getVideoSize(): Pair<Int, Int>? {
if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO)
videoSize = Pair(videoWidth, videoHeight)
if (status != PlayerStatus.ERROR && mediaType == MediaType.VIDEO) videoSize = Pair(videoWidth, videoHeight)
return videoSize
}
@ -664,6 +665,12 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
init {
mediaType = MediaType.UNKNOWN
if (httpDataSourceFactory == null) {
ioScope.launch {
httpDataSourceFactory = OkHttpDataSource.Factory(PodciniHttpClient.getHttpClient() as okhttp3.Call.Factory)
.setUserAgent(ClientConfig.USER_AGENT)
}
}
if (exoPlayer == null) {
setupPlayerListener()
createStaticPlayer(context)
@ -837,6 +844,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
const val BUFFERING_ENDED: Int = -2
const val ERROR_CODE_OFFSET: Int = 1000
private var httpDataSourceFactory: OkHttpDataSource.Factory? = null
private var trackSelector: DefaultTrackSelector? = null
var exoPlayer: ExoPlayer? = null
@ -897,6 +906,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
audioErrorListener = null
bufferingUpdateListener = 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.service.PlaybackServiceTaskManager.PSTMCallback
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.currentlyPlayingFeedMediaId
import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingTemporaryPlaybackSpeed
@ -612,7 +612,7 @@ class PlaybackService : MediaSessionService() {
scope.launch {
try {
val playable = withContext(Dispatchers.IO) {
createInstanceFromPreferences(applicationContext)
loadPlayableFromPreferences()
}
withContext(Dispatchers.Main) {
startPlaying(playable, false)
@ -783,6 +783,7 @@ class PlaybackService : MediaSessionService() {
private fun procFlowEvents() {
scope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlayerErrorEvent -> playerError(event)
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)

View File

@ -1,6 +1,7 @@
package ac.mdiq.podcini.preferences
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.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia
@ -14,7 +15,6 @@ import android.content.Context
import android.content.SharedPreferences
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import android.util.Log
import androidx.preference.PreferenceManager
/**
* 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
private var instance: PlaybackPreferences? = null
private lateinit var prefs: SharedPreferences
// private lateinit var prefs: SharedPreferences
@JvmStatic
fun init(context: Context) {
instance = PlaybackPreferences()
prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.registerOnSharedPreferenceChangeListener(instance)
// prefs = PreferenceManager.getDefaultSharedPreferences(context)
appPrefs.registerOnSharedPreferenceChangeListener(instance)
}
@JvmStatic
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
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
val currentEpisodeIsVideo: Boolean
get() = prefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false)
get() = appPrefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false)
@JvmStatic
val currentPlayerStatus: Int
get() = prefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER)
get() = appPrefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER)
@JvmStatic
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) {
val editor = prefs.edit()
val editor = appPrefs.edit()
editor.putFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, speed)
editor.apply()
}
@JvmStatic
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_FEED_ID, NO_MEDIA_PLAYING)
editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
@ -135,7 +135,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
@JvmStatic
fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus, item: FeedItem? = null) {
Logd(TAG, "Writing playback preferences ${playable?.getIdentifier()}")
val editor = prefs.edit()
val editor = appPrefs.edit()
if (playable == null) {
writeNoMediaPlaying()
@ -162,14 +162,14 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
fun writePlayerStatus(playerStatus: PlayerStatus) {
Logd(TAG, "Writing player status playback preferences")
val editor = prefs.edit()
val editor = appPrefs.edit()
editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus))
editor.apply()
}
@JvmStatic
fun clearCurrentlyPlayingTemporaryPlaybackSpeed() {
val editor = prefs.edit()
val editor = appPrefs.edit()
editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED)
editor.apply()
}
@ -190,40 +190,23 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
* @return The restored Playable object
*/
@JvmStatic
fun createInstanceFromPreferences(context: Context): Playable? {
val currentlyPlayingMedia = currentlyPlayingMediaType
Logd(TAG, "currentlyPlayingMedia: $currentlyPlayingMedia")
if (currentlyPlayingMedia != NO_MEDIA_PLAYING) {
val prefs = PreferenceManager.getDefaultSharedPreferences(context.applicationContext)
return createInstanceFromPreferences(currentlyPlayingMedia.toInt(), prefs)
fun loadPlayableFromPreferences(): Playable? {
val currentlyPlayingType = currentlyPlayingMediaType
Logd(TAG, "loadPlayableFromPreferences currentlyPlayingType: $currentlyPlayingType")
if (currentlyPlayingType != NO_MEDIA_PLAYING) {
val type = currentlyPlayingType.toInt()
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
}
/**
* 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.ui.actions.swipeactions.SwipeAction
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.QueueFragment
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()
}
if (oldVersion < 2040000) {
val swipePrefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE)
swipePrefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + QueueFragment.TAG,
getSharedPrefs(context)
// 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()
}
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"
private lateinit var context: Context
private lateinit var prefs: SharedPreferences
lateinit var appPrefs: SharedPreferences
/**
* Sets up the UserPreferences class.
@ -150,41 +150,41 @@ object UserPreferences {
Logd(TAG, "Creating new instance of UserPreferences")
UserPreferences.context = context.applicationContext
prefs = PreferenceManager.getDefaultSharedPreferences(context)
appPrefs = PreferenceManager.getDefaultSharedPreferences(context)
createNoMediaFile()
}
@JvmStatic
var theme: ThemePreference
get() = when (prefs.getString(PREF_THEME, "system")) {
get() = when (appPrefs.getString(PREF_THEME, "system")) {
"0" -> ThemePreference.LIGHT
"1" -> ThemePreference.DARK
else -> ThemePreference.SYSTEM
}
set(theme) {
when (theme) {
ThemePreference.LIGHT -> prefs.edit().putString(PREF_THEME, "0").apply()
ThemePreference.DARK -> prefs.edit().putString(PREF_THEME, "1").apply()
else -> prefs.edit().putString(PREF_THEME, "system").apply()
ThemePreference.LIGHT -> appPrefs.edit().putString(PREF_THEME, "0").apply()
ThemePreference.DARK -> appPrefs.edit().putString(PREF_THEME, "1").apply()
else -> appPrefs.edit().putString(PREF_THEME, "system").apply()
}
}
val isBlackTheme: Boolean
get() = prefs.getBoolean(PREF_THEME_BLACK, false)
get() = appPrefs.getBoolean(PREF_THEME_BLACK, false)
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
var hiddenDrawerItems: List<String>
get() {
val hiddenItems = prefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "")
val hiddenItems = appPrefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "")
return ArrayList(listOf(*TextUtils.split(hiddenItems, ",")))
}
set(items) {
val str = TextUtils.join(",", items)
prefs.edit()
appPrefs.edit()
.putString(PREF_HIDDEN_DRAWER_ITEMS, str)
.apply()
}
@ -192,7 +192,7 @@ object UserPreferences {
@JvmStatic
var fullNotificationButtons: List<Int>
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()
for (button in buttons) {
notificationButtons.add(button.toInt())
@ -200,8 +200,8 @@ object UserPreferences {
return notificationButtons
}
set(items) {
val str = TextUtils.join(",", items!!)
prefs.edit()
val str = TextUtils.join(",", items)
appPrefs.edit()
.putString(PREF_FULL_NOTIFICATION_BUTTONS, str)
.apply()
}
@ -216,7 +216,7 @@ object UserPreferences {
* @return `true` if button should be shown, `false` otherwise
*/
private fun showButtonOnFullNotification(buttonId: Int): Boolean {
return fullNotificationButtons!!.contains(buttonId)
return fullNotificationButtons.contains(buttonId)
}
@JvmStatic
@ -237,13 +237,13 @@ object UserPreferences {
@JvmStatic
val feedOrder: Int
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()
}
@JvmStatic
fun setFeedOrder(selected: String?) {
prefs.edit()
appPrefs.edit()
.putString(PREF_DRAWER_FEED_ORDER, selected)
.apply()
}
@ -251,7 +251,7 @@ object UserPreferences {
@JvmStatic
val feedCounterSetting: FeedCounter
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())
}
@ -259,14 +259,14 @@ object UserPreferences {
/**
* @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
*/
@JvmStatic
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
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
@ -286,7 +286,7 @@ object UserPreferences {
*
* @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
val isPersistNotify: Boolean
@ -295,23 +295,23 @@ object UserPreferences {
*
* @return `true` if notifications are persistent, `false` otherwise
*/
get() = prefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true)
get() = appPrefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, true)
@JvmStatic
val showDownloadReportRaw: Boolean
/**
* 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 {
return prefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true)
return appPrefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true)
}
@JvmStatic
var enqueueLocation: EnqueueLocation
get() {
val valStr = prefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name)
val valStr = appPrefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name)
try {
return EnqueueLocation.valueOf(valStr!!)
} catch (t: Throwable) {
@ -321,64 +321,64 @@ object UserPreferences {
}
}
set(location) {
prefs.edit().putString(PREF_ENQUEUE_LOCATION, location.name).apply()
appPrefs.edit().putString(PREF_ENQUEUE_LOCATION, location.name).apply()
}
@JvmStatic
val isPauseOnHeadsetDisconnect: Boolean
get() = prefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true)
get() = appPrefs.getBoolean(PREF_PAUSE_ON_HEADSET_DISCONNECT, true)
@JvmStatic
val isUnpauseOnHeadsetReconnect: Boolean
get() = prefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true)
get() = appPrefs.getBoolean(PREF_UNPAUSE_ON_HEADSET_RECONNECT, true)
@JvmStatic
val isUnpauseOnBluetoothReconnect: Boolean
get() = prefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false)
get() = appPrefs.getBoolean(PREF_UNPAUSE_ON_BLUETOOTH_RECONNECT, false)
@JvmStatic
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
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
@set:VisibleForTesting
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(value) {
prefs.edit().putBoolean(PREF_FOLLOW_QUEUE, value).apply()
appPrefs.edit().putBoolean(PREF_FOLLOW_QUEUE, value).apply()
}
@JvmStatic
fun shouldSkipKeepEpisode(): Boolean {
return prefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true)
return appPrefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true)
}
@JvmStatic
fun shouldFavoriteKeepEpisode(): Boolean {
return prefs.getBoolean(PREF_FAVORITE_KEEPS_EPISODE, true)
return appPrefs.getBoolean(PREF_FAVORITE_KEEPS_EPISODE, true)
}
@JvmStatic
val isAutoDelete: Boolean
get() = prefs.getBoolean(PREF_AUTO_DELETE, false)
get() = appPrefs.getBoolean(PREF_AUTO_DELETE, false)
@JvmStatic
val isAutoDeleteLocal: Boolean
get() = prefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false)
get() = appPrefs.getBoolean(PREF_AUTO_DELETE_LOCAL, false)
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
fun shouldDeleteRemoveFromQueue(): Boolean {
return prefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false)
return appPrefs.getBoolean(PREF_DELETE_REMOVES_FROM_QUEUE, false)
}
@JvmStatic
@ -389,7 +389,7 @@ object UserPreferences {
private val audioPlaybackSpeed: Float
get() {
try {
return prefs.getString(PREF_PLAYBACK_SPEED, "1.00")!!.toFloat()
return appPrefs.getString(PREF_PLAYBACK_SPEED, "1.00")!!.toFloat()
} catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e))
setPlaybackSpeed(1.0f)
@ -400,7 +400,7 @@ object UserPreferences {
val videoPlayMode: Int
get() {
try {
return prefs.getString(PREF_VIDEO_MODE, "1")!!.toInt()
return appPrefs.getString(PREF_VIDEO_MODE, "1")!!.toInt()
} catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e))
setVideoMode(1)
@ -412,7 +412,7 @@ object UserPreferences {
var videoPlaybackSpeed: Float
get() {
try {
return prefs.getString(PREF_VIDEO_PLAYBACK_SPEED, "1.00")!!.toFloat()
return appPrefs.getString(PREF_VIDEO_PLAYBACK_SPEED, "1.00")!!.toFloat()
} catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e))
videoPlaybackSpeed = 1.0f
@ -420,21 +420,21 @@ object UserPreferences {
}
}
set(speed) {
prefs.edit()
appPrefs.edit()
.putString(PREF_VIDEO_PLAYBACK_SPEED, speed.toString())
.apply()
}
@JvmStatic
var isSkipSilence: Boolean
get() = prefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false)
get() = appPrefs.getBoolean(PREF_PLAYBACK_SKIP_SILENCE, false)
set(skipSilence) {
prefs.edit().putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence).apply()
appPrefs.edit().putBoolean(PREF_PLAYBACK_SKIP_SILENCE, skipSilence).apply()
}
@JvmStatic
var playbackSpeedArray: List<Float>
get() = readPlaybackSpeedArray(prefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null))
get() = readPlaybackSpeedArray(appPrefs.getString(PREF_PLAYBACK_SPEED_ARRAY, null))
set(speeds) {
val format = DecimalFormatSymbols(Locale.US)
format.decimalSeparator = '.'
@ -443,16 +443,16 @@ object UserPreferences {
for (speed in speeds) {
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
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
get() = prefs.getString(PREF_UPDATE_INTERVAL, "12")!!.toInt().toLong()
get() = appPrefs.getString(PREF_UPDATE_INTERVAL, "12")!!.toInt().toLong()
val isAutoUpdateDisabled: Boolean
get() = updateInterval == 0L
@ -460,7 +460,7 @@ object UserPreferences {
private fun isAllowMobileFor(type: String): Boolean {
val defaultValue = HashSet<String>()
defaultValue.add("images")
val allowed = prefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue)
val allowed = appPrefs.getStringSet(PREF_MOBILE_UPDATE, defaultValue)
return allowed!!.contains(type)
}
@ -509,12 +509,12 @@ object UserPreferences {
private fun setAllowMobileFor(type: String, allow: Boolean) {
val defaultValue = HashSet<String>()
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!!)
if (allow) allowed.add(type)
else allowed.remove(type)
prefs.edit().putStringSet(PREF_MOBILE_UPDATE, allowed).apply()
appPrefs.edit().putStringSet(PREF_MOBILE_UPDATE, allowed).apply()
}
@JvmStatic
@ -524,29 +524,29 @@ object UserPreferences {
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
* 'unlimited'.
*/
get() = prefs.getString(PREF_EPISODE_CACHE_SIZE, "20")!!.toInt()
get() = appPrefs.getString(PREF_EPISODE_CACHE_SIZE, "20")!!.toInt()
@JvmStatic
@set:VisibleForTesting
var isEnableAutodownload: Boolean
get() = prefs.getBoolean(PREF_ENABLE_AUTODL, false)
get() = appPrefs.getBoolean(PREF_ENABLE_AUTODL, false)
set(enabled) {
prefs.edit().putBoolean(PREF_ENABLE_AUTODL, enabled).apply()
appPrefs.edit().putBoolean(PREF_ENABLE_AUTODL, enabled).apply()
}
@JvmStatic
val isEnableAutodownloadOnBattery: Boolean
get() = prefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true)
get() = appPrefs.getBoolean(PREF_ENABLE_AUTODL_ON_BATTERY, true)
@JvmStatic
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
var speedforwardSpeed: Float
get() {
try {
return prefs.getString(PREF_SPEEDFORWRD_SPEED, "0.00")!!.toFloat()
return appPrefs.getString(PREF_SPEEDFORWRD_SPEED, "0.00")!!.toFloat()
} catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e))
speedforwardSpeed = 0.0f
@ -554,14 +554,14 @@ object UserPreferences {
}
}
set(speed) {
prefs.edit().putString(PREF_SPEEDFORWRD_SPEED, speed.toString()).apply()
appPrefs.edit().putString(PREF_SPEEDFORWRD_SPEED, speed.toString()).apply()
}
@JvmStatic
var fallbackSpeed: Float
get() {
try {
return prefs.getString(PREF_FALLBACK_SPEED, "0.00")!!.toFloat()
return appPrefs.getString(PREF_FALLBACK_SPEED, "0.00")!!.toFloat()
} catch (e: NumberFormatException) {
Log.e(TAG, Log.getStackTraceString(e))
fallbackSpeed = 0.0f
@ -569,42 +569,42 @@ object UserPreferences {
}
}
set(speed) {
prefs.edit().putString(PREF_FALLBACK_SPEED, speed.toString()).apply()
appPrefs.edit().putString(PREF_FALLBACK_SPEED, speed.toString()).apply()
}
@JvmStatic
var fastForwardSecs: Int
get() = prefs.getInt(PREF_FAST_FORWARD_SECS, 30)
get() = appPrefs.getInt(PREF_FAST_FORWARD_SECS, 30)
set(secs) {
prefs.edit().putInt(PREF_FAST_FORWARD_SECS, secs).apply()
appPrefs.edit().putInt(PREF_FAST_FORWARD_SECS, secs).apply()
}
@JvmStatic
var rewindSecs: Int
get() = prefs.getInt(PREF_REWIND_SECS, 10)
get() = appPrefs.getInt(PREF_REWIND_SECS, 10)
set(secs) {
prefs.edit().putInt(PREF_REWIND_SECS, secs).apply()
appPrefs.edit().putInt(PREF_REWIND_SECS, secs).apply()
}
@JvmStatic
val autodownloadSelectedNetworks: Array<String>
get() {
val selectedNetWorks = prefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "")
val selectedNetWorks = appPrefs.getString(PREF_AUTODL_SELECTED_NETWORKS, "")
return TextUtils.split(selectedNetWorks, ",")
}
@JvmStatic
var proxyConfig: ProxyConfig
get() {
val type = Proxy.Type.valueOf(prefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name)!!)
val host = prefs.getString(PREF_PROXY_HOST, null)
val port = prefs.getInt(PREF_PROXY_PORT, 0)
val username = prefs.getString(PREF_PROXY_USER, null)
val password = prefs.getString(PREF_PROXY_PASSWORD, null)
val type = Proxy.Type.valueOf(appPrefs.getString(PREF_PROXY_TYPE, Proxy.Type.DIRECT.name)!!)
val host = appPrefs.getString(PREF_PROXY_HOST, null)
val port = appPrefs.getInt(PREF_PROXY_PORT, 0)
val username = appPrefs.getString(PREF_PROXY_USER, null)
val password = appPrefs.getString(PREF_PROXY_PASSWORD, null)
return ProxyConfig(type, host, port, username, password)
}
set(config) {
val editor = prefs.edit()
val editor = appPrefs.edit()
editor.putString(PREF_PROXY_TYPE, config.type.name)
if (config.host.isNullOrEmpty()) editor.remove(PREF_PROXY_HOST)
else editor.putString(PREF_PROXY_HOST, config.host)
@ -623,31 +623,31 @@ object UserPreferences {
@JvmStatic
var isQueueLocked: Boolean
get() = prefs.getBoolean(PREF_QUEUE_LOCKED, false)
get() = appPrefs.getBoolean(PREF_QUEUE_LOCKED, false)
set(locked) {
prefs.edit().putBoolean(PREF_QUEUE_LOCKED, locked).apply()
appPrefs.edit().putBoolean(PREF_QUEUE_LOCKED, locked).apply()
}
@JvmStatic
fun setPlaybackSpeed(speed: Float) {
prefs.edit().putString(PREF_PLAYBACK_SPEED, speed.toString()).apply()
appPrefs.edit().putString(PREF_PLAYBACK_SPEED, speed.toString()).apply()
}
@JvmStatic
fun setVideoMode(mode: Int) {
prefs.edit().putString(PREF_VIDEO_MODE, mode.toString()).apply()
appPrefs.edit().putString(PREF_VIDEO_MODE, mode.toString()).apply()
}
@JvmStatic
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
fun gpodnetNotificationsEnabled(): Boolean {
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
@ -655,11 +655,11 @@ object UserPreferences {
/**
* 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
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> {
@ -682,9 +682,9 @@ object UserPreferences {
@JvmStatic
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) {
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
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()) {
Logd(TAG, "User data folder not writable or not set. Trying default.")
dataFolder = context.getExternalFilesDir(type)
@ -730,7 +730,7 @@ object UserPreferences {
@JvmStatic
fun setDataFolder(dir: String) {
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
var defaultPage: String?
get() = prefs.getString(PREF_DEFAULT_PAGE, "SubscriptionFragment")
get() = appPrefs.getString(PREF_DEFAULT_PAGE, "SubscriptionFragment")
set(defaultPage) {
prefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply()
appPrefs.edit().putString(PREF_DEFAULT_PAGE, defaultPage).apply()
}
@JvmStatic
fun backButtonOpensDrawer(): Boolean {
return prefs.getBoolean(PREF_BACK_OPENS_DRAWER, false)
return appPrefs.getBoolean(PREF_BACK_OPENS_DRAWER, false)
}
@JvmStatic
fun timeRespectsSpeed(): Boolean {
return prefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false)
return appPrefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false)
}
@JvmStatic
var isStreamOverDownload: Boolean
get() = prefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false)
get() = appPrefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false)
set(stream) {
prefs.edit().putBoolean(PREF_STREAM_OVER_DOWNLOAD, stream).apply()
appPrefs.edit().putBoolean(PREF_STREAM_OVER_DOWNLOAD, stream).apply()
}
@JvmStatic
@ -780,14 +780,14 @@ object UserPreferences {
*
* @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.
*
* @see .setQueueKeepSortedOrder
*/
set(keepSorted) {
prefs.edit().putBoolean(PREF_QUEUE_KEEP_SORTED, keepSorted).apply()
appPrefs.edit().putBoolean(PREF_QUEUE_KEEP_SORTED, keepSorted).apply()
}
@JvmStatic
@ -799,7 +799,7 @@ object UserPreferences {
* @see .isQueueKeepSorted
*/
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)
}
/**
@ -809,13 +809,13 @@ object UserPreferences {
*/
set(sortOrder) {
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
val newEpisodesAction: NewEpisodesAction
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())
}
@ -825,14 +825,14 @@ object UserPreferences {
* Returns the sort order for the downloads.
*/
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)
}
/**
* Sets the sort order for the downloads.
*/
set(sortOrder) {
prefs.edit().putString(PREF_DOWNLOADS_SORTED_ORDER, "" + sortOrder!!.code).apply()
appPrefs.edit().putString(PREF_DOWNLOADS_SORTED_ORDER, "" + sortOrder!!.code).apply()
}
// @JvmStatic
@ -855,11 +855,11 @@ object UserPreferences {
@JvmStatic
var subscriptionsFilter: SubscriptionsFilter
get() {
val value = prefs.getString(PREF_FILTER_FEED, "")
val value = appPrefs.getString(PREF_FILTER_FEED, "")
return SubscriptionsFilter(value)
}
set(value) {
prefs.edit().putString(PREF_FILTER_FEED, value.serialize()).apply()
appPrefs.edit().putString(PREF_FILTER_FEED, value.serialize()).apply()
}
@JvmStatic
@ -870,16 +870,16 @@ object UserPreferences {
@JvmStatic
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) {
prefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s!!.code).apply()
appPrefs.edit().putString(PREF_SORT_ALL_EPISODES, "" + s!!.code).apply()
}
@JvmStatic
var prefFilterAllEpisodes: String
get() = prefs.getString(PREF_FILTER_ALL_EPISODES, "")?:""
get() = appPrefs.getString(PREF_FILTER_ALL_EPISODES, "")?:""
set(filter) {
prefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply()
appPrefs.edit().putString(PREF_FILTER_ALL_EPISODES, filter).apply()
}
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.ProxyDialog
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.setDataFolder
import kotlin.Any
@ -32,12 +33,12 @@ class DownloadsPreferencesFragment : PreferenceFragmentCompat(), OnSharedPrefere
override fun onStart() {
super.onStart()
(activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.downloads_pref)
PreferenceManager.getDefaultSharedPreferences(requireContext()).registerOnSharedPreferenceChangeListener(this)
appPrefs.registerOnSharedPreferenceChangeListener(this)
}
override fun onStop() {
super.onStop()
PreferenceManager.getDefaultSharedPreferences(requireContext()).unregisterOnSharedPreferenceChangeListener(this)
appPrefs.unregisterOnSharedPreferenceChangeListener(this)
}
override fun onResume() {

View File

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

View File

@ -37,7 +37,7 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() {
true
}
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() {}
})
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.ui.activity.PreferenceActivity
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.FlowEvent
import android.app.Activity
@ -56,6 +59,7 @@ class SynchronizationPreferencesFragment : PreferenceFragmentCompat() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd("SynchronizationPreferencesFragment", "Received event: ${event}")
when (event) {
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
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.wifi.WifiSyncService.Companion.hostPort
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -96,6 +98,7 @@ import java.util.*
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.SyncServiceEvent -> syncStatusChanged(event)
else -> {}

View File

@ -1,5 +1,6 @@
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.AppWidgetProvider
import android.content.ComponentName
@ -10,6 +11,7 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
import ac.mdiq.podcini.util.Logd
import android.content.SharedPreferences
import java.util.concurrent.TimeUnit
class PlayerWidget : AppWidgetProvider() {
@ -25,10 +27,9 @@ class PlayerWidget : AppWidgetProvider() {
Logd(TAG, "onUpdate() called with: context = [$context], appWidgetManager = [$appWidgetManager], appWidgetIds = [${appWidgetIds.contentToString()}]")
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)
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) {
Logd(TAG, "OnDeleted")
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
for (appWidgetId in appWidgetIds) {
prefs.edit().remove(KEY_WIDGET_COLOR + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_PLAYBACK_SPEED + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_REWIND + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_FAST_FORWARD + appWidgetId).apply()
prefs.edit().remove(KEY_WIDGET_SKIP + appWidgetId).apply()
prefs!!.edit().remove(KEY_WIDGET_COLOR + appWidgetId).apply()
prefs!!.edit().remove(KEY_WIDGET_PLAYBACK_SPEED + appWidgetId).apply()
prefs!!.edit().remove(KEY_WIDGET_REWIND + appWidgetId).apply()
prefs!!.edit().remove(KEY_WIDGET_FAST_FORWARD + appWidgetId).apply()
prefs!!.edit().remove(KEY_WIDGET_SKIP + appWidgetId).apply()
}
val manager = AppWidgetManager.getInstance(context)
val widgetIds = manager.getAppWidgetIds(ComponentName(context, PlayerWidget::class.java))
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)
}
super.onDeleted(context, appWidgetIds)
}
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 {
@ -75,6 +74,12 @@ class PlayerWidget : AppWidgetProvider() {
const val DEFAULT_COLOR: Int = -0xd9d3cf
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) {
// Enqueueing work enables a BOOT_COMPLETED receiver, which in turn makes Android refresh widgets.
// This creates an endless loop with a flickering widget.
@ -87,8 +92,8 @@ class PlayerWidget : AppWidgetProvider() {
@JvmStatic
fun isEnabled(context: Context): Boolean {
val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
return prefs.getBoolean(KEY_ENABLED, false)
// val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
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 androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.concurrent.ExecutionException
/**
* 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.
* Fractional for number of hours, e.g., 0.5 = 12 hours, 0.0416 = 1 hour. */
@JvmField @get:VisibleForTesting val numberOfHoursAfterPlayback: Int
) : EpisodeCleanupAlgorithm() {
/** 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. */
class APCleanupAlgorithm(@JvmField @get:VisibleForTesting val numberOfHoursAfterPlayback: Int) : EpisodeCleanupAlgorithm() {
/**
* @return the number of episodes that *could* be cleaned up, if needed
*/
@ -44,7 +44,7 @@ class APCleanupAlgorithm(
for (item in delete) {
try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get()
runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {
@ -53,7 +53,6 @@ class APCleanupAlgorithm(
}
val counter = delete.size
Log.i(TAG, String.format(Locale.US, "Auto-delete deleted %d episodes (%d requested)", counter, numberOfEpisodesToDelete))
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.FeedItemFilter
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.concurrent.ExecutionException
@ -21,7 +24,7 @@ class APQueueCleanupAlgorithm : EpisodeCleanupAlgorithm() {
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
// 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
for (item in delete) {
if (item.media == null) continue
try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get()
runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {

View File

@ -25,6 +25,7 @@ import android.text.TextUtils
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.concurrent.*
@ -68,15 +69,13 @@ import java.util.concurrent.*
if (feedID != 0L) {
try {
DBWriter.deleteFeed(context, feedID).get()
runBlocking { DBWriter.deleteFeed(context, feedID).join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {
e.printStackTrace()
}
} else {
Log.w(TAG, "removeFeedWithDownloadUrl: Could not find feed with url: $downloadUrl")
}
} else 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? {
if (feed.id != 0L) {
return getFeed(feed.id)
} else {
val feeds = getFeedList()
for (f in feeds.toList()) {
if (f != null && f.identifyingValue == feed.identifyingValue) {
f.items = getFeedItemList(f).toMutableList()
return f
}
if (feed.id != 0L) return getFeed(feed.id)
val feeds = getFeedList()
for (f in feeds.toList()) {
if (f != null && f.identifyingValue == feed.identifyingValue) {
f.items = getFeedItemList(f).toMutableList()
return f
}
}
return null
@ -275,9 +272,8 @@ import java.util.concurrent.*
}
}
if (oldItem != null) {
oldItem.updateFromOther(item)
} else {
if (oldItem != null) oldItem.updateFromOther(item)
else {
Logd(TAG, "Found new item: " + item.title)
item.feed = savedFeed
@ -314,13 +310,13 @@ import java.util.concurrent.*
try {
if (savedFeed == null) {
DBWriter.addNewFeed(context, newFeed).get()
runBlocking { DBWriter.addNewFeed(context, newFeed).join() }
// Update with default values that are set in database
resultFeed = searchFeedByIdentifyingValueOrID(newFeed)
} else DBWriter.persistCompleteFeed(savedFeed).get()
} else runBlocking { DBWriter.persistCompleteFeed(savedFeed).join() }
DBReader.updateFeedList(adapter)
if (removeUnlistedItems) DBWriter.deleteFeedItems(context, unlistedItems).get()
if (removeUnlistedItems) runBlocking { DBWriter.deleteFeedItems(context, unlistedItems).join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} 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.queue.SynchronizationQueueSink
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.writeNoMediaPlaying
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import ac.mdiq.podcini.util.showStackTrace
import android.app.backup.BackupManager
import android.content.Context
import android.net.Uri
@ -37,16 +36,13 @@ import android.util.Log
import androidx.core.app.NotificationManagerCompat
import androidx.documentfile.provider.DocumentFile
import androidx.media3.common.util.UnstableApi
import com.google.common.util.concurrent.Futures
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import java.io.File
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import kotlin.coroutines.ContinuationInterceptor
/**
* Provides methods for writing data to Podcini's database.
@ -57,6 +53,8 @@ import java.util.concurrent.TimeUnit
@UnstableApi object DBWriter {
private const val TAG = "DBWriter"
val ioScope = CoroutineScope(Dispatchers.IO)
private val dbExec: ExecutorService = Executors.newSingleThreadExecutor { r: Runnable? ->
val t = Thread(r)
t.name = "DatabaseExecutor"
@ -77,8 +75,8 @@ import java.util.concurrent.TimeUnit
}
}
fun deleteItemsMedia(items: List<FeedItem>) {
runOnDbThread {
fun deleteItemsMedia(items: List<FeedItem>) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
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.
*/
@JvmStatic
fun deleteFeedMediaOfItem(context: Context, mediaId: Long): Future<*> {
fun deleteFeedMediaOfItem(context: Context, mediaId: Long) : Job {
Logd(TAG, "deleteFeedMediaOfItem called")
return runOnDbThread {
val media = getFeedMedia(mediaId)
@ -179,7 +177,7 @@ import java.util.concurrent.TimeUnit
* @param feedId ID of the Feed that should be deleted.
*/
@JvmStatic
fun deleteFeed(context: Context, feedId: Long): Future<*> {
fun deleteFeed(context: Context, feedId: Long) : Job {
return runOnDbThread {
val feed = getFeed(feedId) ?: return@runOnDbThread
// 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.
* 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")
return runOnDbThread { deleteFeedItemsSynchronous(context, items) }
}
@ -252,7 +250,7 @@ import java.util.concurrent.TimeUnit
/**
* Deletes the entire playback history.
*/
fun clearPlaybackHistory(): Future<*> {
fun clearPlaybackHistory() : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -265,7 +263,7 @@ import java.util.concurrent.TimeUnit
/**
* Deletes the entire download log.
*/
fun clearDownloadLog(): Future<*> {
fun clearDownloadLog() : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -275,8 +273,8 @@ import java.util.concurrent.TimeUnit
}
}
fun deleteFromPlaybackHistory(feedItem: FeedItem): Future<*> {
return addItemToPlaybackHistory(feedItem.media, Date(0))
fun deleteFromPlaybackHistory(feedItem: FeedItem) {
addItemToPlaybackHistory(feedItem.media, Date(0))
}
/**
@ -288,7 +286,7 @@ import java.util.concurrent.TimeUnit
* @param date PlaybackCompletionDate for `media`
*/
@JvmOverloads
fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()): Future<*> {
fun addItemToPlaybackHistory(media: FeedMedia?, date: Date? = Date()) : Job {
return runOnDbThread {
if (media != null) {
Logd(TAG, "Adding item to playback history")
@ -308,7 +306,7 @@ import java.util.concurrent.TimeUnit
*
* @param status The DownloadStatus object.
*/
fun addDownloadStatus(status: DownloadResult?): Future<*> {
fun addDownloadStatus(status: DownloadResult?) : Job {
Logd(TAG, "addDownloadStatus called")
return runOnDbThread {
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
* @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")
return runOnDbThread {
val adapter = getInstance()
@ -357,11 +355,11 @@ import java.util.concurrent.TimeUnit
}
@JvmStatic
fun addQueueItem(context: Context, vararg items: FeedItem): Future<*> {
fun addQueueItem(context: Context, vararg items: FeedItem) : Job {
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")
val itemIds = LongList(items.size)
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 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)
}
@ -393,7 +391,7 @@ import java.util.concurrent.TimeUnit
* @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.
*/
@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")
return runOnDbThread {
if (itemIds.isEmpty()) return@runOnDbThread
@ -406,9 +404,8 @@ import java.util.concurrent.TimeUnit
val markAsUnplayedIds = LongList()
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
val updatedItems: MutableList<FeedItem> = ArrayList()
val positionCalculator =
ItemEnqueuePositionCalculator(enqueueLocation)
val currentlyPlaying = createInstanceFromPreferences(context)
val positionCalculator = ItemEnqueuePositionCalculator(enqueueLocation)
val currentlyPlaying = loadPlayableFromPreferences()
var insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying)
for (itemId in itemIds) {
if (!itemListContains(queue, itemId)) {
@ -468,7 +465,7 @@ import java.util.concurrent.TimeUnit
* Removes all FeedItem objects from the queue.
*/
@JvmStatic
fun clearQueue(): Future<*> {
fun clearQueue() : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -486,19 +483,19 @@ import java.util.concurrent.TimeUnit
* @param item FeedItem that should be removed.
*/
@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) }
}
@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) }
}
@UnstableApi private fun removeQueueItemSynchronous(context: Context, performAutoDownload: Boolean, vararg itemIds: Long) {
Logd(TAG, "removeQueueItemSynchronous called $itemIds")
if (itemIds.isEmpty()) return
showStackTrace()
// showStackTrace()
val adapter = getInstance()
adapter.open()
@ -535,11 +532,12 @@ import java.util.concurrent.TimeUnit
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 {
val adapter = getInstance().open()
adapter.addFavoriteItem(item)
@ -550,7 +548,7 @@ import java.util.concurrent.TimeUnit
}
}
fun removeFavoriteItem(item: FeedItem): Future<*> {
fun removeFavoriteItem(item: FeedItem) : Job {
return runOnDbThread {
val adapter = getInstance().open()
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 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 {
val queueIdList = getQueueIDList()
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 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 {
val queueIdList = getQueueIDList()
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())
*/
@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) }
}
@ -634,7 +632,7 @@ import java.util.concurrent.TimeUnit
adapter.close()
}
fun resetPagedFeedPage(feed: Feed?): Future<*> {
fun resetPagedFeedPage(feed: Feed?) : Job {
return runOnDbThread {
if (feed != null) {
val adapter = getInstance()
@ -652,7 +650,7 @@ import java.util.concurrent.TimeUnit
* FeedItem.UNPLAYED
* @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)
}
@ -665,7 +663,7 @@ import java.util.concurrent.TimeUnit
* This option is usually set to true
* @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 {
val adapter = getInstance()
adapter.open()
@ -683,12 +681,12 @@ import java.util.concurrent.TimeUnit
* FeedItem.NEW, FeedItem.UNPLAYED
* @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
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 {
val adapter = getInstance()
adapter.open()
@ -703,7 +701,7 @@ import java.util.concurrent.TimeUnit
*
* @param feedId ID of the Feed.
*/
fun removeFeedNewFlag(feedId: Long): Future<*> {
fun removeFeedNewFlag(feedId: Long) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -717,7 +715,7 @@ import java.util.concurrent.TimeUnit
* Sets the 'read'-attribute of all NEW FeedItems to UNPLAYED.
*/
@JvmStatic
fun removeAllNewFlags(): Future<*> {
fun removeAllNewFlags() : Job {
return runOnDbThread {
val adapter = getInstance()
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 {
val adapter = getInstance()
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 {
val adapter = getInstance()
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 {
val adapter = getInstance()
adapter.open()
@ -769,7 +767,7 @@ import java.util.concurrent.TimeUnit
*
* @param media The FeedMedia object.
*/
fun persistFeedMedia(media: FeedMedia): Future<*> {
fun persistFeedMedia(media: FeedMedia) : Job {
Logd(TAG, "persistFeedMedia called")
return runOnDbThread {
val adapter = getInstance()
@ -785,7 +783,7 @@ import java.util.concurrent.TimeUnit
* @param media The FeedMedia object.
*/
@JvmStatic
fun persistFeedMediaPlaybackInfo(media: FeedMedia?): Future<*> {
fun persistFeedMediaPlaybackInfo(media: FeedMedia?) : Job {
Logd(TAG, "persistFeedMediaPlaybackInfo called")
return runOnDbThread {
if (media != null) {
@ -804,7 +802,7 @@ import java.util.concurrent.TimeUnit
* @param item The FeedItem object.
*/
@JvmStatic
fun persistFeedItem(item: FeedItem?): Future<*> {
fun persistFeedItem(item: FeedItem?) : Job {
Logd(TAG, "persistFeedItem called")
return runOnDbThread {
if (item != null) {
@ -820,7 +818,7 @@ import java.util.concurrent.TimeUnit
/**
* 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)")
return runOnDbThread {
val adapter = getInstance()
@ -835,7 +833,7 @@ import java.util.concurrent.TimeUnit
*
* @param preferences The FeedPreferences object.
*/
fun persistFeedPreferences(preferences: FeedPreferences): Future<*> {
fun persistFeedPreferences(preferences: FeedPreferences) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -863,7 +861,7 @@ import java.util.concurrent.TimeUnit
*
* @param lastUpdateFailed true if last update failed
*/
fun persistFeedLastUpdateFailed(feedId: Long, lastUpdateFailed: Boolean): Future<*> {
fun persistFeedLastUpdateFailed(feedId: Long, lastUpdateFailed: Boolean) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -873,7 +871,7 @@ import java.util.concurrent.TimeUnit
}
}
fun persistFeedCustomTitle(feed: Feed): Future<*> {
fun persistFeedCustomTitle(feed: Feed) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -890,10 +888,10 @@ import java.util.concurrent.TimeUnit
* QueueUpdateBroadcast. This option should be set to `false`
* 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) {
Log.w(TAG, "reorderQueue() - sortOrder is null. Do nothing.")
return runOnDbThread {}
return Job()
}
val permutor = getPermutor(sortOrder)
return runOnDbThread {
@ -914,7 +912,7 @@ import java.util.concurrent.TimeUnit
* @param feedId The feed's ID
* @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]")
return runOnDbThread {
val adapter = getInstance()
@ -929,7 +927,7 @@ import java.util.concurrent.TimeUnit
* Set item sort order of the feed
*
*/
fun persistFeedItemSortOrder(feedId: Long, sortOrder: SortOrder?): Future<*> {
fun persistFeedItemSortOrder(feedId: Long, sortOrder: SortOrder?) : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
@ -942,32 +940,36 @@ import java.util.concurrent.TimeUnit
/**
* Reset the statistics in DB
*/
// fun resetStatistics(): Future<*> {
// return runOnDbThread {
// val adapter = getInstance()
// adapter.open()
// adapter.resetAllMediaPlayedDuration()
// adapter.close()
// }
// }
suspend fun resetStatistics(): Unit = withContext(Dispatchers.IO) {
val result = async {
fun resetStatistics() : Job {
return runOnDbThread {
val adapter = getInstance()
adapter.open()
adapter.resetAllMediaPlayedDuration()
adapter.close()
}
result.await()
}
/**
* Submit to the DB thread only if caller is not already on the DB thread. Otherwise,
* just execute synchronously
*/
private fun runOnDbThread(runnable: Runnable): Future<*> {
if ("DatabaseExecutor" == Thread.currentThread().name) {
runnable.run()
return Futures.immediateFuture<Any?>(null)
} else return dbExec.submit(runnable)
// private fun runOnDbThread(runnable: Runnable): Future<*> {
// if ("DatabaseExecutor" == Thread.currentThread().name) {
// runnable.run()
// return Futures.immediateFuture<Any?>(null)
// } 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 androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.util.*
import java.util.concurrent.ExecutionException
@ -41,8 +42,9 @@ class ExceptFavoriteCleanupAlgorithm : EpisodeCleanupAlgorithm() {
val delete = if (candidates.size > numberOfEpisodesToDelete) candidates.subList(0, numberOfEpisodesToDelete) else candidates
for (item in delete) {
if (item.media == null) continue
try {
DBWriter.deleteFeedMediaOfItem(context!!, item.media!!.id).get()
runBlocking { DBWriter.deleteFeedMediaOfItem(context, item.media!!.id).join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} catch (e: ExecutionException) {

View File

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

View File

@ -9,7 +9,7 @@ import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.util.LongList
import ac.mdiq.podcini.storage.model.feed.FeedItem
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
@UnstableApi

View File

@ -5,7 +5,7 @@ import android.view.View
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.DBWriter
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
class DeleteActionButton(item: FeedItem) : ItemActionButton(item) {

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.ui.actions.menuhandler
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.wifiSyncEnabledKey
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.ui.activity.MainActivity
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 android.os.Handler
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
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
@ -49,7 +51,6 @@ object FeedItemMenuHandler {
val hasMedia = selectedItem.media != null
val isPlaying = hasMedia && PlaybackStatus.isPlaying(selectedItem.media)
val isInQueue: Boolean = selectedItem.isTagged(FeedItem.TAG_QUEUE)
val fileDownloaded = hasMedia && selectedItem.media?.fileExists()?:false
val isLocalFile = hasMedia && selectedItem.feed?.isLocalFeed?:false
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.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
}

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedItem
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 {
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.storage.model.feed.FeedItemFilter
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.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Canvas
import androidx.annotation.OptIn
import androidx.core.graphics.ColorUtils
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.*
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
@ -26,7 +29,7 @@ import kotlin.math.min
import kotlin.math.sin
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
@ -36,17 +39,19 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
private val itemTouchHelper = ItemTouchHelper(this)
init {
reloadPreference()
fragment.lifecycle.addObserver(this)
actions = getPrefs(fragment.requireContext(), tag)
}
constructor(fragment: Fragment, tag: String) : this(0, fragment, tag)
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun reloadPreference() {
override fun onStart(owner: LifecycleOwner) {
actions = getPrefs(fragment.requireContext(), tag)
}
override fun onStop(owner: LifecycleOwner) {
actions = null
}
fun setFilter(filter: FeedItemFilter?) {
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) {
if (actions != null && !actions!!.hasActions()) {
//open settings dialog if no prefs are set
showDialog()
// SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
// override fun onCall() {
// this@SwipeActions.reloadPreference()
// EventBus.getDefault().post(SwipeActionsChangedEvent())
// }
// })
return
}
val item = (viewHolder as EpisodeItemViewHolder).feedItem
if (actions != null && item != null && filter != null)
(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() {
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
override fun onCall() {
this@SwipeActions.reloadPreference()
actions = getPrefs(fragment.requireContext(), tag)
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) {
swipeOutEnabled = false
val swipeThresholdReached = displacementPercentage == 1f
// Move slower when getting near the maxMovement
dx = sign * maxMovement * sin((Math.PI / 2) * displacementPercentage)
.toFloat()
dx = sign * maxMovement * sin((Math.PI / 2) * displacementPercentage).toFloat()
if (isCurrentlyActive) {
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 {
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_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
val swipeActions: List<SwipeAction> = Collections.unmodifiableList(
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 {
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)
}
@ -224,12 +222,12 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
return getPrefs(context, tag, "")
}
@JvmStatic
@OptIn(UnstableApi::class) @JvmStatic
fun getPrefsWithDefaults(context: Context, tag: String): Actions {
val defaultActions = when (tag) {
QueueFragment.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
else -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION
}
@ -238,8 +236,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
@JvmStatic
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.R
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.restartUpdateAlarm
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.hiddenDrawerItems
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.DBWriter.ioScope
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.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -110,7 +116,18 @@ class MainActivity : CastEnabledActivity() {
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))
@ -145,9 +162,8 @@ class MainActivity : CastEnabledActivity() {
val fm = supportFragmentManager
if (fm.findFragmentByTag(MAIN_FRAGMENT_TAG) == null) {
if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) {
loadFragment(defaultPage, null)
} else {
if (UserPreferences.DEFAULT_PAGE_REMEMBER != defaultPage) loadFragment(defaultPage, null)
else {
val lastFragment = NavDrawerFragment.getLastNavFragment(this)
if (ArrayUtils.contains(NavDrawerFragment.NAV_DRAWER_TAGS, lastFragment)) {
loadFragment(lastFragment, null)
@ -173,14 +189,15 @@ class MainActivity : CastEnabledActivity() {
navDrawer = findViewById(R.id.navDrawerFragment)
audioPlayerFragmentView = findViewById(R.id.audioplayerFragment)
checkFirstLaunch()
ioScope.launch { checkFirstLaunch() }
this.bottomSheet = BottomSheetBehavior.from(audioPlayerFragmentView) as LockableBottomSheetBehavior<*>
this.bottomSheet.isHideable = false
this.bottomSheet.isDraggable = false
this.bottomSheet.setBottomSheetCallback(bottomSheetCallback)
restartUpdateAlarm(this, false)
SynchronizationQueueSink.syncNowIfNotSyncedRecently()
ioScope.launch { SynchronizationQueueSink.syncNowIfNotSyncedRecently() }
WorkManager.getInstance(this)
.getWorkInfosByTagLiveData(FeedUpdateManager.WORK_TAG_FEED_UPDATE)
@ -376,7 +393,7 @@ class MainActivity : CastEnabledActivity() {
QueueFragment.TAG -> fragment = QueueFragment()
AllEpisodesFragment.TAG -> fragment = AllEpisodesFragment()
DownloadsFragment.TAG -> fragment = DownloadsFragment()
PlaybackHistoryFragment.TAG -> fragment = PlaybackHistoryFragment()
HistoryFragment.TAG -> fragment = HistoryFragment()
AddFeedFragment.TAG -> fragment = AddFeedFragment()
SubscriptionFragment.TAG -> fragment = SubscriptionFragment()
StatisticsFragment.TAG -> fragment = StatisticsFragment()
@ -389,7 +406,7 @@ class MainActivity : CastEnabledActivity() {
}
if (args != null) fragment.arguments = args
NavDrawerFragment.saveLastNavFragment(this, tag)
ioScope.launch { NavDrawerFragment.saveLastNavFragment(this@MainActivity, tag) }
loadFragment(fragment)
}
@ -560,6 +577,7 @@ class MainActivity : CastEnabledActivity() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.MessageEvent -> onEventMainThread(event)
else -> {}
@ -660,7 +678,7 @@ class MainActivity : CastEnabledActivity() {
val feature = uri.getQueryParameter("page") ?: return
when (feature) {
"DOWNLOADS" -> loadFragment(DownloadsFragment.TAG, null)
"HISTORY" -> loadFragment(PlaybackHistoryFragment.TAG, null)
"HISTORY" -> loadFragment(HistoryFragment.TAG, null)
"EPISODES" -> loadFragment(AllEpisodesFragment.TAG, null)
"QUEUE" -> loadFragment(QueueFragment.TAG, null)
"SUBSCRIPTIONS" -> loadFragment(SubscriptionFragment.TAG, null)

View File

@ -161,6 +161,7 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd("PreferenceActivity", "Received event: ${event}")
when (event) {
is FlowEvent.MessageEvent -> onEventMainThread(event)
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.playback.Playable
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.VideoEpisodeFragment
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
@ -169,6 +171,7 @@ class VideoplayerActivity : CastEnabledActivity() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) supportInvalidateOptionsMenu()
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.preferences.ThemeSwitcher.getTheme
import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
class WidgetConfigActivity : AppCompatActivity() {
@ -95,13 +96,13 @@ class WidgetConfigActivity : AppCompatActivity() {
}
private fun setInitialState() {
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
ckPlaybackSpeed.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, true)
ckRewind.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, true)
ckFastForward.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, true)
ckSkip.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, true)
// val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
ckPlaybackSpeed.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, true)
ckRewind.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, true)
ckFastForward.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, true)
ckSkip.isChecked = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, true)
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
opacitySeekBar.setProgress(opacity, false)
@ -122,8 +123,8 @@ class WidgetConfigActivity : AppCompatActivity() {
private fun confirmCreateWidget() {
val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
val editor = prefs.edit()
// val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
val editor = prefs!!.edit()
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor)
editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.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.utils.ThemeUtils
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Logd
import android.R.color
import android.app.Activity
import android.util.Log
@ -18,9 +19,10 @@ import java.lang.ref.WeakReference
/**
* List adapter for the list of new episodes.
*/
open class EpisodeItemListAdapter(mainActivity: MainActivity) :
SelectableAdapter<EpisodeItemViewHolder?>(mainActivity), View.OnCreateContextMenuListener {
open class EpisodeItemListAdapter(mainActivity: MainActivity)
: SelectableAdapter<EpisodeItemViewHolder?>(mainActivity), View.OnCreateContextMenuListener {
val TAG = "EpisodeItemListAdapter"
val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private var episodes: List<FeedItem> = ArrayList()
@ -49,6 +51,8 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
}
@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)
}
@ -79,24 +83,13 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
}
holder.infoCard.setOnClickListener {
val activity: MainActivity? = mainActivityRef.get()
if (!inActionMode()) {
// val ids: LongArray = FeedItemUtil.getIds(episodes)
// val position = ArrayUtils.indexOf(ids, item.id)
activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
} else {
toggleSelection(holder.bindingAdapterPosition)
}
if (!inActionMode()) activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
else toggleSelection(holder.bindingAdapterPosition)
}
holder.coverHolder.setOnClickListener {
val activity: MainActivity? = mainActivityRef.get()
if (!inActionMode()) {
// val ids: LongArray = FeedItemUtil.getIds(episodes)
// val position = ArrayUtils.indexOf(ids, item.id)
activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
} else {
toggleSelection(holder.bindingAdapterPosition)
}
if (!inActionMode()) activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
else toggleSelection(holder.bindingAdapterPosition)
}
holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent ->
if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
@ -106,12 +99,11 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
}
false
})
if (inActionMode()) {
holder.secondaryActionButton.setOnClickListener(null)
if (isSelected(pos)) {
if (isSelected(pos))
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)

View File

@ -4,13 +4,14 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.NavListitemBinding
import ac.mdiq.podcini.databinding.NavSectionItemBinding
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.hiddenDrawerItems
import ac.mdiq.podcini.storage.DBWriter.ioScope
import ac.mdiq.podcini.storage.NavDrawerData.FeedDrawerItem
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.util.Logd
import android.app.Activity
import android.content.DialogInterface
import android.content.Intent
@ -28,6 +29,9 @@ import androidx.media3.common.util.UnstableApi
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
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 java.lang.ref.WeakReference
import java.text.NumberFormat
@ -49,9 +53,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
init {
loadItems()
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs.registerOnSharedPreferenceChangeListener(this)
appPrefs.registerOnSharedPreferenceChangeListener(this@NavListAdapter)
}
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
AllEpisodesFragment.TAG -> R.drawable.ic_feed
DownloadsFragment.TAG -> R.drawable.ic_download
PlaybackHistoryFragment.TAG -> R.drawable.ic_history
HistoryFragment.TAG -> R.drawable.ic_history
SubscriptionFragment.TAG -> R.drawable.ic_subscriptions
StatisticsFragment.TAG -> R.drawable.ic_chart_box
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]))
}

View File

@ -11,8 +11,7 @@ import android.util.Log
/**
* Used by Recyclerviews that need to provide ability to select items.
*/
abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) :
RecyclerView.Adapter<T>() {
abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activity: Activity) : RecyclerView.Adapter<T>() {
private var actionMode: ActionMode? = null
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.ui.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.FeedItemlistFragment
import ac.mdiq.podcini.ui.utils.CoverLoader
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.os.Build
import android.view.*
import android.widget.*
import androidx.appcompat.content.res.AppCompatResources
@ -153,6 +153,7 @@ open class SubscriptionsAdapter(mainActivity: MainActivity)
}
inner class SubscriptionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val binding = SubscriptionItemBinding.bind(itemView)
private val title = binding.titleLabel
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.storage.model.feed.Feed
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.runBlocking
import java.lang.ref.WeakReference
import java.util.*
import java.util.concurrent.ExecutionException
@ -37,7 +38,7 @@ import java.util.concurrent.ExecutionException
@UnstableApi private fun onConfirmed(original: String, updated: String) {
try {
DBWriter.updateFeedDownloadURL(original, updated).get()
runBlocking { DBWriter.updateFeedDownloadURL(original, updated).join() }
feed.download_url = updated
runOnce(activityRef.get()!!, feed)
} catch (e: ExecutionException) {

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.EpisodeFilterDialogBinding
import ac.mdiq.podcini.storage.model.feed.FeedFilter
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.DialogInterface
import android.view.LayoutInflater

View File

@ -10,10 +10,8 @@ import android.content.DialogInterface
import android.util.Log
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import java.lang.Runnable
object RemoveFeedDialog {
private const val TAG = "RemoveFeedDialog"
@ -63,7 +61,7 @@ object RemoveFeedDialog {
try {
withContext(Dispatchers.IO) {
for (feed in feeds) {
DBWriter.deleteFeed(context, feed.id).get()
runBlocking { DBWriter.deleteFeed(context, feed.id).join() }
}
}
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.SwipeActions
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.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 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 ->
(!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)
keys = Stream.of(keys).toList()
}
else -> {}
}
if (tag != QueueFragment.TAG) {
keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_QUEUE) }.toList()
}
if (tag != QueueFragment.TAG) keys = Stream.of(keys).filter { a: SwipeAction -> !a.getId().equals(SwipeAction.REMOVE_FROM_QUEUE) }.toList()
builder.setTitle(context.getString(R.string.swipeactions_label) + " - " + forFragment)
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)) {
icon.setTint(getColorFromAttr(context, action.getActionColor()))
item.swipeActionLabel.setTextColor(getColorFromAttr(context, action.getActionColor()))
} else {
icon.setTint(getColorFromAttr(context, R.attr.action_icon_color))
}
item.swipeIcon.setImageDrawable(icon)
} else icon.setTint(getColorFromAttr(context, R.attr.action_icon_color))
item.swipeIcon.setImageDrawable(icon)
item.root.setOnClickListener {
if (direction == LEFT) leftAction = 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?) {
val prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + tag, "$right,$left").apply()
getSharedPrefs(context)
SwipeActions.prefs!!.edit().putString(SwipeActions.KEY_PREFIX_SWIPEACTIONS + tag, "$right,$left").apply()
}
private fun saveActionsEnabledPrefs(enabled: Boolean) {
val prefs = context.getSharedPreferences(SwipeActions.PREF_NAME, Context.MODE_PRIVATE)
prefs.edit().putBoolean(SwipeActions.KEY_PREFIX_NO_ACTION + tag, enabled).apply()
getSharedPrefs(context)
SwipeActions.prefs!!.edit().putBoolean(SwipeActions.KEY_PREFIX_NO_ACTION + tag, enabled).apply()
}
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.model.feed.FeedPreferences
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.FlowEvent
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.preferences.UserPreferences.isSkipSilence
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.util.Logd
import ac.mdiq.podcini.util.event.EventFlow

View File

@ -1,6 +1,8 @@
package ac.mdiq.podcini.ui.fragment
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.prefFilterAllEpisodes
import ac.mdiq.podcini.storage.DBReader
@ -99,6 +101,7 @@ import org.apache.commons.lang3.StringUtils
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.AllEpisodesFilterChangedEvent -> onFilterChanged(event)
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.cast.CastEnabledActivity
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.videoPlayMode
import ac.mdiq.podcini.receiver.MediaButtonReceiver
@ -309,9 +310,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun procFlowEvents() {
lifecycleScope.launch {
Logd(TAG, "subscribing PositionFlowEvent")
EventFlow.events.collectLatest { event ->
// Logd(TAG, "PositionFlowEvent: ${event}")
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackServiceEvent ->
if (event.action == FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN)
@ -546,7 +546,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
// Logd(TAG, "PositionFlowEvent: ${event}")
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackPositionEvent -> onPositionObserverUpdate(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.SelectableAdapter
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.LiftOnScrollListener
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd
@ -104,6 +104,7 @@ import kotlinx.coroutines.withContext
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView)
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(getFilter())
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener {
@ -423,6 +424,7 @@ import kotlinx.coroutines.withContext
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.SwipeActionsChangedEvent -> refreshSwipeTelltale()
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.base.MediaPlayerBase
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.playback.Playable
import ac.mdiq.podcini.ui.adapter.ChaptersListAdapter
@ -124,6 +126,7 @@ class ChaptersFragment : AppCompatDialogFragment() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
else -> {}

View File

@ -5,6 +5,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FragmentItunesSearchBinding
import ac.mdiq.podcini.databinding.SelectCountryDialogBinding
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.storage.DBReader
import ac.mdiq.podcini.ui.activity.MainActivity
@ -42,7 +43,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FragmentItunesSearchBinding? = null
private val binding get() = _binding!!
private lateinit var prefs: SharedPreferences
// private lateinit var prefs: SharedPreferences
private lateinit var gridView: GridView
private lateinit var progressBar: ProgressBar
private lateinit var txtvError: TextView
@ -91,10 +92,10 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
countryCode = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)
hidden = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)
needsConfirm = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)
// prefs = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
countryCode = prefs!!.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)
hidden = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)
needsConfirm = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)
}
@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.setText(R.string.discover_confirm)
butRetry.setOnClickListener {
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
needsConfirm = false
loadToplist(country)
}
@ -225,7 +226,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
R.id.discover_hide_item -> {
item.setChecked(!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())
loadToplist(countryCode)
@ -278,8 +279,8 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
hidden = false
}
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs!!.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
EventFlow.postEvent(FlowEvent.DiscoveryDefaultUpdateEvent())
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.ui.adapter.DownloadLogAdapter
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -83,6 +83,7 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.DownloadLogEvent -> loadDownloadLog()
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.SelectableAdapter
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.LiftOnScrollListener
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd
@ -97,6 +97,7 @@ import java.util.*
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener {
@ -213,6 +214,7 @@ import java.util.*
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)

View File

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

View File

@ -105,6 +105,7 @@ import kotlin.math.min
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.AllEpisodesFilterChangedEvent -> page = 1
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.statistics.StatisticsFragment
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.Logd
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.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
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.dialog.*
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.util.*
import ac.mdiq.podcini.util.event.EventFlow
@ -112,6 +113,7 @@ import java.util.concurrent.Semaphore
binding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView)
lifecycle.addObserver(swipeActions)
refreshSwipeTelltale()
binding.header.leftActionIcon.setOnClickListener {
swipeActions.showDialog()
@ -257,7 +259,7 @@ import java.util.concurrent.Semaphore
feed!!.nextPageLink = feed!!.download_url
feed!!.pageNr = 0
try {
DBWriter.resetPagedFeedPage(feed).get()
runBlocking { DBWriter.resetPagedFeedPage(feed).join() }
FeedUpdateManager.runOnce(requireContext(), feed)
} catch (e: ExecutionException) {
throw RuntimeException(e)
@ -352,6 +354,7 @@ import java.util.concurrent.Semaphore
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.QueueEvent -> loadItems()
is FlowEvent.FavoritesEvent -> loadItems()

View File

@ -286,7 +286,7 @@ class FeedSettingsFragment : Fragment() {
val setPreferencesFuture = DBWriter.persistFeedPreferences(feedPreferences!!)
Thread({
try {
setPreferencesFuture.get()
runBlocking { setPreferencesFuture.join() }
} catch (e: InterruptedException) {
e.printStackTrace()
} 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.dialog.ConfirmationDialog
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.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.DateFormatter
@ -28,18 +27,18 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import java.util.*
@UnstableApi class PlaybackHistoryFragment : BaseEpisodesListFragment() {
@UnstableApi class HistoryFragment : BaseEpisodesListFragment() {
private var sortOrder : SortOrder = SortOrder.PLAYED_DATE_NEW_OLD
private var startDate : Long = 0L
private var endDate : Long = Date().time
override fun getFragmentTag(): String {
return "PlaybackHistoryFragment"
return TAG
}
override fun getPrefName(): String {
return "PlaybackHistoryFragment"
return TAG
}
@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?) {
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)
@ -131,6 +130,7 @@ import java.util.*
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.HistoryEvent -> {
sortOrder = event.sortOrder
@ -177,6 +177,6 @@ import java.util.*
}
companion object {
const val TAG: String = "PlaybackHistoryFragment"
const val TAG: String = "HistoryFragment"
}
}

View File

@ -82,9 +82,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
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?
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
navAdapter = NavListAdapter(itemAccess, requireActivity())
@ -96,7 +96,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
startActivity(Intent(activity, PreferenceActivity::class.java))
}
preferences.registerOnSharedPreferenceChangeListener(this)
prefs!!.registerOnSharedPreferenceChangeListener(this)
return binding.root
}
@ -125,12 +125,13 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
_binding = null
// scope.cancel()
requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).unregisterOnSharedPreferenceChangeListener(this)
prefs!!.unregisterOnSharedPreferenceChangeListener(this)
}
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.FeedListUpdateEvent -> loadData()
is FlowEvent.QueueEvent -> onQueueChanged(event)
@ -292,6 +293,10 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
@VisibleForTesting
const val PREF_NAME: String = "NavDrawerPrefs"
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
@JvmField
@ -301,15 +306,15 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
QueueFragment.TAG,
AllEpisodesFragment.TAG,
DownloadsFragment.TAG,
PlaybackHistoryFragment.TAG,
HistoryFragment.TAG,
StatisticsFragment.TAG,
AddFeedFragment.TAG,
)
fun saveLastNavFragment(context: Context, tag: String?) {
Logd(TAG, "saveLastNavFragment(tag: $tag)")
val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val edit: SharedPreferences.Editor = prefs.edit()
// val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val edit: SharedPreferences.Editor = prefs!!.edit()
if (tag != null) edit.putString(PREF_LAST_FRAGMENT_TAG, tag)
else edit.remove(PREF_LAST_FRAGMENT_TAG)
@ -317,9 +322,9 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
}
fun getLastNavFragment(context: Context): String {
val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val lastFragment: String = prefs.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionFragment.TAG)?:""
Logd(TAG, "getLastNavFragment() -> $lastFragment")
// val prefs: SharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
val lastFragment: String = prefs!!.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionFragment.TAG)?:""
// Logd(TAG, "getLastNavFragment() -> $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.HttpDownloader
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.receiver.PlayerWidget.Companion.PREFS_NAME
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBTasks
import ac.mdiq.podcini.storage.DBWriter
@ -35,6 +38,7 @@ import android.app.Dialog
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.DialogInterface
import android.content.SharedPreferences
import android.graphics.LightingColorFilter
import android.os.Bundle
import android.text.Spannable
@ -330,6 +334,7 @@ import kotlin.concurrent.Volatile
@OptIn(UnstableApi::class) private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
else -> {}
@ -507,8 +512,8 @@ import kotlin.concurrent.Volatile
}
if (isEnableAutodownload) {
val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
binding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
// val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
binding.autoDownloadCheckBox.isChecked = prefs!!.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
}
if (alternateFeedUrls.isEmpty()) {
@ -587,8 +592,8 @@ import kotlin.concurrent.Volatile
val autoDownload = binding.autoDownloadCheckBox.isChecked
feedPreferences.autoDownload = autoDownload
val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
val editor = preferences.edit()
// val preferences = requireContext().getSharedPreferences(PREFS, MODE_PRIVATE)
val editor = prefs!!.edit()
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload)
editor.apply()
}
@ -738,6 +743,12 @@ import kotlin.concurrent.Volatile
private const val PREF_LAST_AUTO_DOWNLOAD = "lastAutoDownload"
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
fun newInstance(feedUrl: String): 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.feed.util.ImageResourceUtils
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.DBWriter.persistFeedItem
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.FeedMedia
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.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.view.ShownotesWebView
@ -25,9 +29,7 @@ import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
import android.content.*
import android.graphics.ColorFilter
import android.os.Build
import android.os.Bundle
@ -409,8 +411,8 @@ class PlayerDetailsFragment : Fragment() {
@UnstableApi private fun savePreference() {
Logd(TAG, "Saving preferences")
val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val editor = prefs.edit()
// val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val editor = prefs!!.edit()
if (controller?.getMedia() != null) {
Logd(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY)
editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY)
@ -429,9 +431,9 @@ class PlayerDetailsFragment : Fragment() {
Logd(TAG, "Restoring from preferences")
val activity: Activity? = activity
if (activity != null) {
val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val id = prefs.getString(PREF_PLAYABLE_ID, "")
val scrollY = prefs.getInt(PREF_SCROLL_Y, -1)
// val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val id = prefs!!.getString(PREF_PLAYABLE_ID, "")
val scrollY = prefs!!.getInt(PREF_SCROLL_Y, -1)
if (scrollY != -1) {
if (id == controller?.getMedia()?.getIdentifier()?.toString()) {
Logd(TAG, "Restored scroll Position: $scrollY")
@ -455,6 +457,7 @@ class PlayerDetailsFragment : Fragment() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.PlaybackPositionEvent -> onEventMainThread(event)
else -> {}
@ -525,5 +528,10 @@ class PlayerDetailsFragment : Fragment() {
private const val PREF = "ItemDescriptionFragmentPrefs"
private const val PREF_SCROLL_Y = "prefScrollY"
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.dialog.ConfirmationDialog
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.LiftOnScrollListener
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Converter
import ac.mdiq.podcini.util.FeedItemUtil
@ -72,7 +72,6 @@ import java.util.*
private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var swipeActions: SwipeActions
private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView
private lateinit var progressBar: ProgressBar
@ -88,7 +87,7 @@ import java.util.*
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
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 {
@ -122,15 +121,12 @@ import java.util.*
recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
swipeActions = QueueSwipeActions()
lifecycle.addObserver(swipeActions)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions.attachTo(recyclerView)
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener {
swipeActions.showDialog()
}
binding.rightActionIcon.setOnClickListener {
swipeActions.showDialog()
}
binding.leftActionIcon.setOnClickListener { swipeActions.showDialog() }
binding.rightActionIcon.setOnClickListener { swipeActions.showDialog() }
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
@ -143,9 +139,7 @@ import java.util.*
swipeRefreshLayout = binding.swipeRefresh
swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
swipeRefreshLayout.setOnRefreshListener { FeedUpdateManager.runOnceOrAsk(requireContext()) }
emptyView = EmptyViewHandler(requireContext())
emptyView.attachToRecyclerView(recyclerView)
@ -200,6 +194,7 @@ import java.util.*
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.QueueEvent -> onEventMainThread(event)
is FlowEvent.FeedItemEvent -> onEventMainThread(event)
@ -406,7 +401,7 @@ import java.util.*
val isLocked: Boolean = UserPreferences.isQueueLocked
if (isLocked) setQueueLocked(false)
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)
else {
val builder = MaterialAlertDialogBuilder(requireContext())
@ -419,7 +414,7 @@ import java.util.*
builder.setView(view)
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)
}
builder.setNegativeButton(R.string.cancel_label, null)
@ -586,7 +581,6 @@ import java.util.*
// Update tracked position
if (dragFrom == -1) dragFrom = fromPosition
dragTo = toPosition
val from = viewHolder.bindingAdapterPosition
@ -632,5 +626,11 @@ import java.util.*
private const val PREFS = "QueueFragment"
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.databinding.QuickFeedDiscoveryBinding
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.storage.DBReader
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.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
@ -96,6 +95,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.DiscoveryDefaultUpdateEvent -> loadToplist()
else -> {}
@ -111,9 +111,9 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
poweredByTextView.visibility = View.VISIBLE
val loader = ItunesTopListLoader(requireContext())
val prefs: SharedPreferences = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
val countryCode: String = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)!!
if (prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) {
// val prefs: SharedPreferences = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
val countryCode: String = prefs!!.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)!!
if (prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) {
errorTextView.setText(R.string.discover_is_hidden)
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.GONE
@ -121,7 +121,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
poweredByTextView.visibility = View.GONE
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 = ""
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.VISIBLE
@ -129,7 +129,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
errorRetry.setText(R.string.discover_confirm)
poweredByTextView.visibility = View.VISIBLE
errorRetry.setOnClickListener {
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
loadToplist()
}
return
@ -185,7 +185,6 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
errorRetry.setOnClickListener { loadToplist() }
}
}
}
@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.HorizontalFeedListAdapter
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.LiftOnScrollListener
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.Logd
@ -239,6 +239,7 @@ import kotlinx.coroutines.withContext
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.FeedListUpdateEvent, is FlowEvent.UnreadItemsUpdateEvent, is FlowEvent.PlayerStatusEvent -> search()
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.dialog.FeedSortDialog
import ac.mdiq.podcini.ui.dialog.SubscriptionsFilterDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.*
@ -62,7 +60,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var progressBar: ProgressBar
private lateinit var prefs: SharedPreferences
// private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView
private var tagFilterIndex = 1
@ -78,7 +76,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
super.onCreate(savedInstanceState)
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 {
@ -236,6 +234,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.FeedListUpdateEvent -> onFeedListChanged(event)
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.base.MediaPlayerBase
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.rewindSecs
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
@ -155,6 +157,7 @@ class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.BufferUpdateEvent -> bufferUpdate(event)
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.databinding.PagerFragmentBinding
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.PREFS_NAME
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.ui.activity.MainActivity
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 android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@ -98,7 +100,7 @@ class StatisticsFragment : PagedToolbarFragment() {
}
@UnstableApi private fun doResetStatistics() {
requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE).edit()
prefs!!.edit()
.putBoolean(PREF_INCLUDE_MARKED_PLAYED, false)
.putLong(PREF_FILTER_FROM, 0)
.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_TO: String = "filterTo"
private const val POS_SUBSCRIPTIONS = 0
private const val POS_YEARS = 1
private const val POS_SPACE_TAKEN = 2
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 filterDatesTo: Pair<Array<String>, Array<Long>>
init {
initParams()
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.databinding.StatisticsFragmentBinding
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.StatisticsItem
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment.Companion
import ac.mdiq.podcini.ui.statistics.StatisticsFragment.Companion.prefs
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.*
@ -21,10 +19,6 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
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.flow.collectLatest
import kotlinx.coroutines.launch
@ -77,6 +71,7 @@ class SubscriptionStatisticsFragment : Fragment() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.StatisticsEvent -> refreshStatistics()
else -> {}
@ -96,7 +91,7 @@ class SubscriptionStatisticsFragment : Fragment() {
if (statisticsResult != null) {
val dialog = object: DatesFilterDialog(requireContext(), statisticsResult!!.oldestDate) {
override fun initParams() {
prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
prefs = StatisticsFragment.prefs
includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
@ -126,10 +121,10 @@ class SubscriptionStatisticsFragment : Fragment() {
private fun loadStatistics() {
// disposable?.dispose()
val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
val includeMarkedAsPlayed = prefs.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
val timeFilterFrom = prefs.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
val timeFilterTo = prefs.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
// val prefs = requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
val includeMarkedAsPlayed = prefs!!.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
val timeFilterFrom = prefs!!.getLong(StatisticsFragment.PREF_FILTER_FROM, 0)
val timeFilterTo = prefs!!.getLong(StatisticsFragment.PREF_FILTER_TO, Long.MAX_VALUE)
// disposable = Observable.fromCallable {
// val statisticsData = DBReader.getStatistics(

View File

@ -3,8 +3,11 @@ package ac.mdiq.podcini.ui.statistics.years
import ac.mdiq.podcini.R
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.MonthlyStatisticsItem
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
import android.os.Bundle
@ -65,6 +68,7 @@ class YearsStatisticsFragment : Fragment() {
private fun procFlowEvents() {
lifecycleScope.launch {
EventFlow.events.collectLatest { event ->
Logd(TAG, "Received event: $event")
when (event) {
is FlowEvent.StatisticsEvent -> refreshStatistics()
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.ui.activity.MainActivity
import ac.mdiq.podcini.util.Logd
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
@ -75,6 +76,7 @@ class CoverLoader(private val activity: MainActivity) {
.setHeader("User-Agent", "Mozilla/5.0")
.listener(object : ImageRequest.Listener {
override fun onError(request: ImageRequest, throwable: ErrorResult) {
Logd("CoverLoader", "Trying to get fallback image")
val fallbackImageRequest = ImageRequest.Builder(activity)
.data(fallbackUri)
.setHeader("User-Agent", "Mozilla/5.0")
@ -86,9 +88,7 @@ class CoverLoader(private val activity: MainActivity) {
})
.target(coverTargetCoil)
.build()
activity.imageLoader
.enqueue(request)
activity.imageLoader.enqueue(request)
}
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.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.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.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 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.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

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.ui.view
package ac.mdiq.podcini.ui.utils
import android.content.Context
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 {
NONE, FADE, SLIDE

View File

@ -8,6 +8,8 @@ import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ac.mdiq.podcini.R
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.PREFS_NAME
import android.content.SharedPreferences
class EpisodeItemListRecyclerView : RecyclerView {
private lateinit var layoutManager: LinearLayoutManager
@ -46,16 +48,16 @@ class EpisodeItemListRecyclerView : RecyclerView {
val firstItemView = layoutManager.findViewByPosition(firstItem)
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_OFFSET + tag, topOffset.toInt())
.apply()
}
fun restoreScrollPosition(tag: String) {
val prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
val position = prefs.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0)
val offset = prefs.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0)
// val prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE)
val position = prefs!!.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0)
val offset = prefs!!.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0)
if (position > 0 || offset > 0) layoutManager.scrollToPositionWithOffset(position, offset)
}
@ -71,5 +73,11 @@ class EpisodeItemListRecyclerView : RecyclerView {
private const val TAG = "EpisodeItemListRecyclerView"
private const val PREF_PREFIX_SCROLL_POSITION = "scroll_position_"
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
import ac.mdiq.podcini.ui.activity.MainActivity
import android.os.Build
import android.text.Layout
import android.text.format.Formatter
import android.util.Log
@ -18,7 +17,7 @@ import com.google.android.material.elevation.SurfaceColors
import com.joanzapata.iconify.Iconify
import ac.mdiq.podcini.R
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.net.download.MediaSizeLoader
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 android.widget.LinearLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat.getDrawable
import io.reactivex.functions.Consumer
import kotlin.math.max
@ -45,8 +43,8 @@ import kotlin.math.max
* Holds the view which shows FeedItems.
*/
@UnstableApi
open class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)) {
open class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup)
: RecyclerView.ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.feeditemlist_item, parent, false)) {
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
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
if (!(actionButton?.TAG == TTSActionButton::class.simpleName && newButton.TAG == TTSActionButton::class.simpleName)) {
actionButton = newButton
@ -118,11 +116,9 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
// Log.d(TAG, "bind called ${item.media}")
when {
item.media != null -> {
bind(item.media!!)
}
item.media != null -> bind(item.media!!)
// for generating TTS files for episode without media
item.playState == BUILDING -> {
// for generating TTS files for episode without media
secondaryActionProgress.setPercentage(actionButton!!.processing, item)
secondaryActionProgress.setIndeterminate(false)
}
@ -139,15 +135,16 @@ open class EpisodeItemViewHolder(private val activity: MainActivity, parent: Vie
if (coverHolder.visibility == View.VISIBLE) {
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(item)
// Logd(TAG, "imgLoc $imgLoc ${item.feed?.imageUrl} ${item.title}")
if (!imgLoc.isNullOrBlank() && !imgLoc.contains(PREFIX_GENERATIVE_COVER)) CoverLoader(activity)
.withUri(imgLoc)
.withFallbackUri(item.feed?.imageUrl)
.withPlaceholderView(placeholder)
.withCoverView(cover)
.load()
Logd(TAG, "imgLoc $imgLoc ${item.feed?.imageUrl} ${item.title}")
if (!imgLoc.isNullOrBlank() && !imgLoc.contains(PREFIX_GENERATIVE_COVER))
CoverLoader(activity)
.withUri(imgLoc)
.withFallbackUri(item.feed?.imageUrl)
.withPlaceholderView(placeholder)
.withCoverView(cover)
.load()
else {
Logd(TAG, "setting to ic_launcher")
Logd(TAG, "setting cover to ic_launcher")
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() -> {
size.text = "{fa-spinner}"
Iconify.addIcons(size)
MediaSizeLoader.getFeedMediaSizeObservable(media).subscribe(
Consumer<Long?> { sizeValue: Long? ->
if (sizeValue == null) return@Consumer
if (sizeValue > 0) size.text = Formatter.formatShortFileSize(activity, sizeValue)
else size.text = ""
}) { error: Throwable? ->
MediaSizeLoader.getFeedMediaSizeObservable(media).subscribe(Consumer<Long?> { sizeValue: Long? ->
if (sizeValue == null) return@Consumer
if (sizeValue > 0) size.text = Formatter.formatShortFileSize(activity, sizeValue)
else size.text = ""
}) { error: Throwable? ->
size.text = ""
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.PlayerWidget
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.Playable
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.util.Converter.getDurationStringLong
import ac.mdiq.podcini.util.TimeSpeedConverter
import android.R.attr.bitmap
import android.appwidget.AppWidgetManager
import android.content.ComponentName
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Matrix
import android.graphics.drawable.BitmapDrawable
import android.util.Log
import android.view.KeyEvent
@ -165,16 +163,16 @@ object WidgetUpdater {
for (id in widgetIds) {
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 columns = getCellsForSize(minWidth)
if (columns < 3) views.setViewVisibility(R.id.layout_center, View.INVISIBLE)
else views.setViewVisibility(R.id.layout_center, View.VISIBLE)
val showPlaybackSpeed = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + id, true)
val showRewind = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + id, true)
val showFastForward = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + id, true)
val showSkip = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + id, true)
val showPlaybackSpeed = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + id, true)
val showRewind = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + id, true)
val showFastForward = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + id, true)
val showSkip = prefs!!.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + id, true)
if (showPlaybackSpeed || showRewind || showSkip || showFastForward) {
views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.VISIBLE)
@ -188,7 +186,7 @@ object WidgetUpdater {
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)
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.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.util.Logd
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.
*/
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)))
else WidgetUpdater.updateWidget(applicationContext, WidgetState(PlayerStatus.STOPPED))
}

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.util
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.receiver.SPAReceiver
import android.content.Context
import android.content.Intent
@ -31,12 +32,12 @@ object SPAUtil {
Log.wtf(TAG, "Unable to get application context")
return false
}
val prefs = PreferenceManager.getDefaultSharedPreferences(appContext)
if (!prefs.getBoolean(PREF_HAS_QUERIED_SP_APPS, false)) {
// val prefs = PreferenceManager.getDefaultSharedPreferences(appContext)
if (!appPrefs.getBoolean(PREF_HAS_QUERIED_SP_APPS, false)) {
appContext.sendBroadcast(Intent(SPAReceiver.ACTION_SP_APPS_QUERY_FEEDS))
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.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.proxyConfig
import ac.mdiq.podcini.net.download.service.DownloadServiceInterfaceImpl
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.File
@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