6.5.6 commit
This commit is contained in:
parent
c2977301f6
commit
25f811a8bd
|
@ -31,8 +31,8 @@ android {
|
|||
testApplicationId "ac.mdiq.podcini.tests"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
versionCode 3020239
|
||||
versionName "6.5.5"
|
||||
versionCode 3020240
|
||||
versionName "6.5.6"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
@ -142,7 +142,6 @@ android {
|
|||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard.cfg"
|
||||
resValue "string", "app_name", "Podcini.R"
|
||||
resValue "string", "provider_authority", "ac.mdiq.podcini.R.provider"
|
||||
// debuggable false
|
||||
vcsInfo.include false
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
|
@ -180,18 +179,11 @@ dependencies {
|
|||
|
||||
implementation 'com.github.XilinJia.vistaguide:VistaGuide:lv0.24.2.6'
|
||||
|
||||
// implementation 'androidx.compose.material3:material3:1.2.0'
|
||||
implementation 'androidx.compose.material:material:1.7.0'
|
||||
// implementation 'androidx.compose.foundation:foundation:1.6.2'
|
||||
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview:1.7.0'
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling:1.7.0'
|
||||
|
||||
// Optional - Add full set of material icons
|
||||
// implementation 'androidx.compose.material:material-icons-extended'
|
||||
// Optional - Add window size utils
|
||||
// implementation 'androidx.compose.material3:material3-window-size-class'
|
||||
|
||||
implementation 'androidx.activity:activity-compose:1.9.2'
|
||||
implementation 'androidx.window:window:1.3.0'
|
||||
|
||||
|
@ -204,7 +196,6 @@ dependencies {
|
|||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation "androidx.fragment:fragment-ktx:1.8.3"
|
||||
implementation 'androidx.gridlayout:gridlayout:1.0.0'
|
||||
// implementation "androidx.media:media:1.7.0"
|
||||
implementation "androidx.media3:media3-exoplayer:1.4.1"
|
||||
implementation "androidx.media3:media3-ui:1.4.1"
|
||||
implementation "androidx.media3:media3-datasource-okhttp:1.4.1"
|
||||
|
@ -233,7 +224,6 @@ dependencies {
|
|||
implementation "com.squareup.okhttp3:okhttp-urlconnection:4.12.0"
|
||||
implementation 'com.squareup.okio:okio:3.9.0'
|
||||
|
||||
// implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
implementation "io.reactivex.rxjava3:rxjava:3.1.8"
|
||||
implementation "io.reactivex.rxjava3:rxandroid:3.0.2"
|
||||
|
|
|
@ -45,8 +45,8 @@
|
|||
android:supportsRtl="true"
|
||||
android:logo="@mipmap/ic_launcher"
|
||||
android:resizeableActivity="true"
|
||||
android:allowAudioPlaybackCapture="true">
|
||||
<!-- android:networkSecurityConfig="@xml/network_security_config">-->
|
||||
android:allowAudioPlaybackCapture="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<service android:name=".playback.service.PlaybackService"
|
||||
android:foregroundServiceType="mediaPlayback"
|
||||
|
|
|
@ -5,16 +5,11 @@ import ac.mdiq.podcini.playback.base.InTheatre.curState
|
|||
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.currentMediaType
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isCasting
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isRunning
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackService
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
||||
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -191,28 +186,5 @@ abstract class ServiceStatusHandler(private val activity: FragmentActivity) {
|
|||
|
||||
companion object {
|
||||
private val TAG: String = ServiceStatusHandler::class.simpleName ?: "Anonymous"
|
||||
|
||||
/**
|
||||
* Returns an intent which starts an audio- or videoplayer, depending on the
|
||||
* type of media that is being played. If the playbackservice is not
|
||||
* running, the type of the last played media will be looked up.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPlayerActivityIntent(context: Context): Intent {
|
||||
val showVideoPlayer = if (isRunning) currentMediaType == MediaType.VIDEO && !isCasting
|
||||
else curState.curIsVideo
|
||||
return if (showVideoPlayer) VideoPlayerActivityStarter(context).intent
|
||||
else MainActivityStarter(context).withOpenPlayer().getIntent()
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as [.getPlayerActivityIntent], but here the type of activity
|
||||
* depends on the medaitype that is provided as an argument.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPlayerActivityIntent(context: Context, mediaType: MediaType?): Intent {
|
||||
return if (mediaType == MediaType.VIDEO && !isCasting) VideoPlayerActivityStarter(context).intent
|
||||
else MainActivityStarter(context).withOpenPlayer().getIntent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package ac.mdiq.podcini.playback.base
|
||||
|
||||
enum class VideoMode(val code: Int, val tag: String) {
|
||||
NONE(0, "none"),
|
||||
WINDOW_VIEW(1, "window mode"),
|
||||
FULL_SCREEN_VIEW(2, "full screen"),
|
||||
AUDIO_ONLY(3, "audio only");
|
||||
|
||||
companion object {
|
||||
val videoModeTags = VideoMode.entries.map { it.tag }
|
||||
|
||||
fun fromCode(code: Int): VideoMode {
|
||||
return enumValues<VideoMode>().firstOrNull { it.code == code } ?: NONE
|
||||
}
|
||||
fun fromTag(tag: String): VideoMode {
|
||||
return enumValues<VideoMode>().firstOrNull { it.tag == tag } ?: NONE
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,12 +12,10 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
|||
import ac.mdiq.podcini.playback.base.MediaPlayerBase
|
||||
import ac.mdiq.podcini.playback.base.MediaPlayerCallback
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.Feed
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.storage.model.Playable
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
|
@ -197,7 +195,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
val audioStream = audioStreamsList[audioIndex]
|
||||
Logd(TAG, "setDataSource1 use audio quality: ${audioStream.bitrate}")
|
||||
val aSource = DefaultMediaSourceFactory(context).createMediaSource(MediaItem.Builder().setTag(metadata).setUri(Uri.parse(audioStream.content)).build())
|
||||
if (media.episode?.feed?.preferences?.playAudioOnly != true) {
|
||||
if (media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) {
|
||||
Logd(TAG, "setDataSource1 result: $streamInfo")
|
||||
Logd(TAG, "setDataSource1 videoStreams: ${streamInfo.videoStreams.size} videoOnlyStreams: ${streamInfo.videoOnlyStreams.size} audioStreams: ${streamInfo.audioStreams.size}")
|
||||
val videoStreamsList = getSortedStreamVideosList(streamInfo.videoStreams, streamInfo.videoOnlyStreams, true, true)
|
||||
|
|
|
@ -52,6 +52,8 @@ import ac.mdiq.podcini.storage.model.CurrentState.Companion.PLAYER_STATUS_PLAYIN
|
|||
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
|
||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil
|
||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
|
||||
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
||||
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.ui.utils.NotificationUtils
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
|
@ -1440,5 +1442,18 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an intent which starts an audio- or videoplayer, depending on the
|
||||
* type of media that is being played or the medaitype that is provided as an argument.
|
||||
* If the playbackservice is not running, the type of the last played media will be looked up.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getPlayerActivityIntent(context: Context, mediaType_: MediaType? = null): Intent {
|
||||
val mediaType = mediaType_ ?: currentMediaType
|
||||
val showVideoPlayer = if (isRunning) mediaType == MediaType.VIDEO && !isCasting else curState.curIsVideo
|
||||
return if (showVideoPlayer) VideoPlayerActivityStarter(context).intent
|
||||
else MainActivityStarter(context).withOpenPlayer().getIntent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,269 @@ import java.net.Proxy
|
|||
object UserPreferences {
|
||||
private val TAG: String = UserPreferences::class.simpleName ?: "Anonymous"
|
||||
|
||||
// Experimental
|
||||
const val EPISODE_CLEANUP_QUEUE: Int = -1
|
||||
const val EPISODE_CLEANUP_NULL: Int = -2
|
||||
const val EPISODE_CLEANUP_EXCEPT_FAVORITE: Int = -3
|
||||
const val EPISODE_CLEANUP_DEFAULT: Int = 0
|
||||
|
||||
const val EPISODE_CACHE_SIZE_UNLIMITED: Int = -1
|
||||
|
||||
const val DEFAULT_PAGE_REMEMBER: String = "remember"
|
||||
|
||||
private lateinit var context: Context
|
||||
lateinit var appPrefs: SharedPreferences
|
||||
|
||||
var theme: ThemePreference
|
||||
get() = when (appPrefs.getString(Prefs.prefTheme.name, "system")) {
|
||||
"0" -> ThemePreference.LIGHT
|
||||
"1" -> ThemePreference.DARK
|
||||
else -> ThemePreference.SYSTEM
|
||||
}
|
||||
set(theme) {
|
||||
when (theme) {
|
||||
ThemePreference.LIGHT -> appPrefs.edit().putString(Prefs.prefTheme.name, "0").apply()
|
||||
ThemePreference.DARK -> appPrefs.edit().putString(Prefs.prefTheme.name, "1").apply()
|
||||
else -> appPrefs.edit().putString(Prefs.prefTheme.name, "system").apply()
|
||||
}
|
||||
}
|
||||
|
||||
val isBlackTheme: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefThemeBlack.name, false)
|
||||
|
||||
val isThemeColorTinted: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= 31 && appPrefs.getBoolean(Prefs.prefTintedColors.name, false)
|
||||
|
||||
var hiddenDrawerItems: List<String>
|
||||
get() {
|
||||
val hiddenItems = appPrefs.getString(Prefs.prefHiddenDrawerItems.name, "")
|
||||
return hiddenItems?.split(",") ?: listOf()
|
||||
}
|
||||
set(items) {
|
||||
val str = items.joinToString()
|
||||
appPrefs.edit().putString(Prefs.prefHiddenDrawerItems.name, str).apply()
|
||||
}
|
||||
|
||||
var fullNotificationButtons: List<Int>
|
||||
get() {
|
||||
val buttons = appPrefs.getString(Prefs.prefFullNotificationButtons.name, "${NOTIFICATION_BUTTON.SKIP.ordinal},${NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal}")?.split(",") ?: listOf()
|
||||
val notificationButtons: MutableList<Int> = ArrayList()
|
||||
for (button in buttons) {
|
||||
notificationButtons.add(button.toInt())
|
||||
}
|
||||
return notificationButtons
|
||||
}
|
||||
set(items) {
|
||||
val str = items.joinToString()
|
||||
appPrefs.edit().putString(Prefs.prefFullNotificationButtons.name, str).apply()
|
||||
}
|
||||
|
||||
val isAutoDelete: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefAutoDelete.name, false)
|
||||
|
||||
val isAutoDeleteLocal: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefAutoDeleteLocal.name, false)
|
||||
|
||||
val videoPlayMode: Int
|
||||
get() {
|
||||
try { return appPrefs.getString(Prefs.prefVideoPlaybackMode.name, "1")!!.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
setVideoMode(1)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
var videoPlaybackSpeed: Float
|
||||
get() {
|
||||
try { return appPrefs.getString(Prefs.prefVideoPlaybackSpeed.name, "1.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
videoPlaybackSpeed = 1.0f
|
||||
return 1.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit().putString(Prefs.prefVideoPlaybackSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
var isSkipSilence: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefSkipSilence.name, false)
|
||||
set(skipSilence) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefSkipSilence.name, skipSilence).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the capacity of the episode cache. This method will return the
|
||||
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
|
||||
* 'unlimited'.
|
||||
*/
|
||||
val episodeCacheSize: Int
|
||||
get() = appPrefs.getString(Prefs.prefEpisodeCacheSize.name, "20")!!.toInt()
|
||||
|
||||
@set:VisibleForTesting
|
||||
var isEnableAutodownload: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefEnableAutoDl.name, false)
|
||||
set(enabled) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefEnableAutoDl.name, enabled).apply()
|
||||
}
|
||||
|
||||
val isEnableAutodownloadOnBattery: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefEnableAutoDownloadOnBattery.name, true)
|
||||
|
||||
var speedforwardSpeed: Float
|
||||
get() {
|
||||
try { return appPrefs.getString(Prefs.prefSpeedforwardSpeed.name, "0.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
speedforwardSpeed = 0.0f
|
||||
return 0.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit().putString(Prefs.prefSpeedforwardSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
var fallbackSpeed: Float
|
||||
get() {
|
||||
try { return appPrefs.getString(Prefs.prefFallbackSpeed.name, "0.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
fallbackSpeed = 0.0f
|
||||
return 0.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit().putString(Prefs.prefFallbackSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
var fastForwardSecs: Int
|
||||
get() = appPrefs.getInt(Prefs.prefFastForwardSecs.name, 30)
|
||||
set(secs) {
|
||||
appPrefs.edit().putInt(Prefs.prefFastForwardSecs.name, secs).apply()
|
||||
}
|
||||
|
||||
var rewindSecs: Int
|
||||
get() = appPrefs.getInt(Prefs.prefRewindSecs.name, 10)
|
||||
set(secs) {
|
||||
appPrefs.edit().putInt(Prefs.prefRewindSecs.name, secs).apply()
|
||||
}
|
||||
|
||||
var proxyConfig: ProxyConfig
|
||||
get() {
|
||||
val type = Proxy.Type.valueOf(appPrefs.getString(Prefs.prefProxyType.name, Proxy.Type.DIRECT.name)!!)
|
||||
val host = appPrefs.getString(Prefs.prefProxyHost.name, null)
|
||||
val port = appPrefs.getInt(Prefs.prefProxyPort.name, 0)
|
||||
val username = appPrefs.getString(Prefs.prefProxyUser.name, null)
|
||||
val password = appPrefs.getString(Prefs.prefProxyPassword.name, null)
|
||||
return ProxyConfig(type, host, port, username, password)
|
||||
}
|
||||
set(config) {
|
||||
val editor = appPrefs.edit()
|
||||
editor.putString(Prefs.prefProxyType.name, config.type.name)
|
||||
if (config.host.isNullOrEmpty()) editor.remove(Prefs.prefProxyHost.name)
|
||||
else editor.putString(Prefs.prefProxyHost.name, config.host)
|
||||
|
||||
if (config.port <= 0 || config.port > 65535) editor.remove(Prefs.prefProxyPort.name)
|
||||
else editor.putInt(Prefs.prefProxyPort.name, config.port)
|
||||
|
||||
if (config.username.isNullOrEmpty()) editor.remove(Prefs.prefProxyUser.name)
|
||||
else editor.putString(Prefs.prefProxyUser.name, config.username)
|
||||
|
||||
if (config.password.isNullOrEmpty()) editor.remove(Prefs.prefProxyPassword.name)
|
||||
else editor.putString(Prefs.prefProxyPassword.name, config.password)
|
||||
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
var defaultPage: String?
|
||||
get() = appPrefs.getString(Prefs.prefDefaultPage.name, "SubscriptionsFragment")
|
||||
set(defaultPage) {
|
||||
appPrefs.edit().putString(Prefs.prefDefaultPage.name, defaultPage).apply()
|
||||
}
|
||||
|
||||
var isStreamOverDownload: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefStreamOverDownload.name, false)
|
||||
set(stream) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefStreamOverDownload.name, stream).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the UserPreferences class.
|
||||
* @throws IllegalArgumentException if context is null
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
Logd(TAG, "Creating new instance of UserPreferences")
|
||||
UserPreferences.context = context.applicationContext
|
||||
FilesUtils.context = context.applicationContext
|
||||
appPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
createNoMediaFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return whether the specified button should be shown on full
|
||||
* notifications.
|
||||
* @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD,
|
||||
* NOTIFICATION_BUTTON.SKIP.ordinal, NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal
|
||||
* or NOTIFICATION_BUTTON.NEXT_CHAPTER.ordinal.
|
||||
* @return `true` if button should be shown, `false` otherwise
|
||||
*/
|
||||
private fun showButtonOnFullNotification(buttonId: Int): Boolean {
|
||||
return fullNotificationButtons.contains(buttonId)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showSkipOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.SKIP.ordinal)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showNextChapterOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.NEXT_CHAPTER.ordinal)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showPlaybackSpeedOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if we should show remaining time or the duration
|
||||
*/
|
||||
fun shouldShowRemainingTime(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.showTimeLeft.name, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for whether we show the remain time, if not show the duration. This will
|
||||
* send out events so the current playing screen, queue and the episode list would refresh
|
||||
* @return `true` if we should show remaining time or the duration
|
||||
*/
|
||||
fun setShowRemainTimeSetting(showRemain: Boolean?) {
|
||||
appPrefs.edit().putBoolean(Prefs.showTimeLeft.name, showRemain!!).apply()
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun shouldPauseForFocusLoss(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefPauseForFocusLoss.name, true)
|
||||
}
|
||||
|
||||
fun backButtonOpensDrawer(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefBackButtonOpensDrawer.name, false)
|
||||
}
|
||||
|
||||
fun timeRespectsSpeed(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefPlaybackTimeRespectsSpeed.name, false)
|
||||
}
|
||||
|
||||
fun setPlaybackSpeed(speed: Float) {
|
||||
appPrefs.edit().putString(Prefs.prefPlaybackSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
fun setVideoMode(mode: Int) {
|
||||
appPrefs.edit().putString(Prefs.prefVideoPlaybackMode.name, mode.toString()).apply()
|
||||
}
|
||||
|
||||
@Suppress("EnumEntryName")
|
||||
enum class Prefs {
|
||||
prefOPMLBackup,
|
||||
|
@ -109,12 +372,6 @@ object UserPreferences {
|
|||
prefVideoPlaybackMode,
|
||||
}
|
||||
|
||||
// Experimental
|
||||
const val EPISODE_CLEANUP_QUEUE: Int = -1
|
||||
const val EPISODE_CLEANUP_NULL: Int = -2
|
||||
const val EPISODE_CLEANUP_EXCEPT_FAVORITE: Int = -3
|
||||
const val EPISODE_CLEANUP_DEFAULT: Int = 0
|
||||
|
||||
// Constants
|
||||
enum class NOTIFICATION_BUTTON {
|
||||
REWIND,
|
||||
|
@ -124,274 +381,6 @@ object UserPreferences {
|
|||
PLAYBACK_SPEED,
|
||||
}
|
||||
|
||||
const val EPISODE_CACHE_SIZE_UNLIMITED: Int = -1
|
||||
|
||||
const val DEFAULT_PAGE_REMEMBER: String = "remember"
|
||||
|
||||
private lateinit var context: Context
|
||||
lateinit var appPrefs: SharedPreferences
|
||||
|
||||
var theme: ThemePreference
|
||||
get() = when (appPrefs.getString(Prefs.prefTheme.name, "system")) {
|
||||
"0" -> ThemePreference.LIGHT
|
||||
"1" -> ThemePreference.DARK
|
||||
else -> ThemePreference.SYSTEM
|
||||
}
|
||||
set(theme) {
|
||||
when (theme) {
|
||||
ThemePreference.LIGHT -> appPrefs.edit().putString(Prefs.prefTheme.name, "0").apply()
|
||||
ThemePreference.DARK -> appPrefs.edit().putString(Prefs.prefTheme.name, "1").apply()
|
||||
else -> appPrefs.edit().putString(Prefs.prefTheme.name, "system").apply()
|
||||
}
|
||||
}
|
||||
|
||||
val isBlackTheme: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefThemeBlack.name, false)
|
||||
|
||||
val isThemeColorTinted: Boolean
|
||||
get() = Build.VERSION.SDK_INT >= 31 && appPrefs.getBoolean(Prefs.prefTintedColors.name, false)
|
||||
|
||||
var hiddenDrawerItems: List<String>
|
||||
get() {
|
||||
val hiddenItems = appPrefs.getString(Prefs.prefHiddenDrawerItems.name, "")
|
||||
return hiddenItems?.split(",") ?: listOf()
|
||||
}
|
||||
set(items) {
|
||||
val str = items.joinToString()
|
||||
appPrefs.edit()
|
||||
.putString(Prefs.prefHiddenDrawerItems.name, str)
|
||||
.apply()
|
||||
}
|
||||
|
||||
var fullNotificationButtons: List<Int>
|
||||
get() {
|
||||
val buttons = appPrefs.getString(Prefs.prefFullNotificationButtons.name, "${NOTIFICATION_BUTTON.SKIP.ordinal},${NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal}")?.split(",") ?: listOf()
|
||||
val notificationButtons: MutableList<Int> = ArrayList()
|
||||
for (button in buttons) {
|
||||
notificationButtons.add(button.toInt())
|
||||
}
|
||||
return notificationButtons
|
||||
}
|
||||
set(items) {
|
||||
val str = items.joinToString()
|
||||
appPrefs.edit()
|
||||
.putString(Prefs.prefFullNotificationButtons.name, str)
|
||||
.apply()
|
||||
}
|
||||
|
||||
val isAutoDelete: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefAutoDelete.name, false)
|
||||
|
||||
val isAutoDeleteLocal: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefAutoDeleteLocal.name, false)
|
||||
|
||||
val videoPlayMode: Int
|
||||
get() {
|
||||
try {
|
||||
return appPrefs.getString(Prefs.prefVideoPlaybackMode.name, "1")!!.toInt()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
setVideoMode(1)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
var videoPlaybackSpeed: Float
|
||||
get() {
|
||||
try {
|
||||
return appPrefs.getString(Prefs.prefVideoPlaybackSpeed.name, "1.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
videoPlaybackSpeed = 1.0f
|
||||
return 1.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit()
|
||||
.putString(Prefs.prefVideoPlaybackSpeed.name, speed.toString())
|
||||
.apply()
|
||||
}
|
||||
|
||||
var isSkipSilence: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefSkipSilence.name, false)
|
||||
set(skipSilence) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefSkipSilence.name, skipSilence).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the capacity of the episode cache. This method will return the
|
||||
* negative integer EPISODE_CACHE_SIZE_UNLIMITED if the cache size is set to
|
||||
* 'unlimited'.
|
||||
*/
|
||||
val episodeCacheSize: Int
|
||||
get() = appPrefs.getString(Prefs.prefEpisodeCacheSize.name, "20")!!.toInt()
|
||||
|
||||
@set:VisibleForTesting
|
||||
var isEnableAutodownload: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefEnableAutoDl.name, false)
|
||||
set(enabled) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefEnableAutoDl.name, enabled).apply()
|
||||
}
|
||||
|
||||
val isEnableAutodownloadOnBattery: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefEnableAutoDownloadOnBattery.name, true)
|
||||
|
||||
var speedforwardSpeed: Float
|
||||
get() {
|
||||
try {
|
||||
return appPrefs.getString(Prefs.prefSpeedforwardSpeed.name, "0.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
speedforwardSpeed = 0.0f
|
||||
return 0.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit().putString(Prefs.prefSpeedforwardSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
var fallbackSpeed: Float
|
||||
get() {
|
||||
try {
|
||||
return appPrefs.getString(Prefs.prefFallbackSpeed.name, "0.00")!!.toFloat()
|
||||
} catch (e: NumberFormatException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
fallbackSpeed = 0.0f
|
||||
return 0.0f
|
||||
}
|
||||
}
|
||||
set(speed) {
|
||||
appPrefs.edit().putString(Prefs.prefFallbackSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
var fastForwardSecs: Int
|
||||
get() = appPrefs.getInt(Prefs.prefFastForwardSecs.name, 30)
|
||||
set(secs) {
|
||||
appPrefs.edit().putInt(Prefs.prefFastForwardSecs.name, secs).apply()
|
||||
}
|
||||
|
||||
var rewindSecs: Int
|
||||
get() = appPrefs.getInt(Prefs.prefRewindSecs.name, 10)
|
||||
set(secs) {
|
||||
appPrefs.edit().putInt(Prefs.prefRewindSecs.name, secs).apply()
|
||||
}
|
||||
|
||||
var proxyConfig: ProxyConfig
|
||||
get() {
|
||||
val type = Proxy.Type.valueOf(appPrefs.getString(Prefs.prefProxyType.name, Proxy.Type.DIRECT.name)!!)
|
||||
val host = appPrefs.getString(Prefs.prefProxyHost.name, null)
|
||||
val port = appPrefs.getInt(Prefs.prefProxyPort.name, 0)
|
||||
val username = appPrefs.getString(Prefs.prefProxyUser.name, null)
|
||||
val password = appPrefs.getString(Prefs.prefProxyPassword.name, null)
|
||||
return ProxyConfig(type, host, port, username, password)
|
||||
}
|
||||
set(config) {
|
||||
val editor = appPrefs.edit()
|
||||
editor.putString(Prefs.prefProxyType.name, config.type.name)
|
||||
if (config.host.isNullOrEmpty()) editor.remove(Prefs.prefProxyHost.name)
|
||||
else editor.putString(Prefs.prefProxyHost.name, config.host)
|
||||
|
||||
if (config.port <= 0 || config.port > 65535) editor.remove(Prefs.prefProxyPort.name)
|
||||
else editor.putInt(Prefs.prefProxyPort.name, config.port)
|
||||
|
||||
if (config.username.isNullOrEmpty()) editor.remove(Prefs.prefProxyUser.name)
|
||||
else editor.putString(Prefs.prefProxyUser.name, config.username)
|
||||
|
||||
if (config.password.isNullOrEmpty()) editor.remove(Prefs.prefProxyPassword.name)
|
||||
else editor.putString(Prefs.prefProxyPassword.name, config.password)
|
||||
|
||||
editor.apply()
|
||||
}
|
||||
|
||||
var defaultPage: String?
|
||||
get() = appPrefs.getString(Prefs.prefDefaultPage.name, "SubscriptionsFragment")
|
||||
set(defaultPage) {
|
||||
appPrefs.edit().putString(Prefs.prefDefaultPage.name, defaultPage).apply()
|
||||
}
|
||||
|
||||
var isStreamOverDownload: Boolean
|
||||
get() = appPrefs.getBoolean(Prefs.prefStreamOverDownload.name, false)
|
||||
set(stream) {
|
||||
appPrefs.edit().putBoolean(Prefs.prefStreamOverDownload.name, stream).apply()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the UserPreferences class.
|
||||
* @throws IllegalArgumentException if context is null
|
||||
*/
|
||||
fun init(context: Context) {
|
||||
Logd(TAG, "Creating new instance of UserPreferences")
|
||||
UserPreferences.context = context.applicationContext
|
||||
FilesUtils.context = context.applicationContext
|
||||
appPrefs = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
createNoMediaFile()
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to return whether the specified button should be shown on full
|
||||
* notifications.
|
||||
* @param buttonId Either NOTIFICATION_BUTTON_REWIND, NOTIFICATION_BUTTON_FAST_FORWARD,
|
||||
* NOTIFICATION_BUTTON.SKIP.ordinal, NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal
|
||||
* or NOTIFICATION_BUTTON.NEXT_CHAPTER.ordinal.
|
||||
* @return `true` if button should be shown, `false` otherwise
|
||||
*/
|
||||
private fun showButtonOnFullNotification(buttonId: Int): Boolean {
|
||||
return fullNotificationButtons.contains(buttonId)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showSkipOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.SKIP.ordinal)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showNextChapterOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.NEXT_CHAPTER.ordinal)
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun showPlaybackSpeedOnFullNotification(): Boolean {
|
||||
return showButtonOnFullNotification(NOTIFICATION_BUTTON.PLAYBACK_SPEED.ordinal)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return `true` if we should show remaining time or the duration
|
||||
*/
|
||||
fun shouldShowRemainingTime(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.showTimeLeft.name, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the preference for whether we show the remain time, if not show the duration. This will
|
||||
* send out events so the current playing screen, queue and the episode list would refresh
|
||||
* @return `true` if we should show remaining time or the duration
|
||||
*/
|
||||
fun setShowRemainTimeSetting(showRemain: Boolean?) {
|
||||
appPrefs.edit().putBoolean(Prefs.showTimeLeft.name, showRemain!!).apply()
|
||||
}
|
||||
|
||||
// only used in test
|
||||
fun shouldPauseForFocusLoss(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefPauseForFocusLoss.name, true)
|
||||
}
|
||||
|
||||
fun backButtonOpensDrawer(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefBackButtonOpensDrawer.name, false)
|
||||
}
|
||||
|
||||
fun timeRespectsSpeed(): Boolean {
|
||||
return appPrefs.getBoolean(Prefs.prefPlaybackTimeRespectsSpeed.name, false)
|
||||
}
|
||||
|
||||
fun setPlaybackSpeed(speed: Float) {
|
||||
appPrefs.edit().putString(Prefs.prefPlaybackSpeed.name, speed.toString()).apply()
|
||||
}
|
||||
|
||||
fun setVideoMode(mode: Int) {
|
||||
appPrefs.edit().putString(Prefs.prefVideoPlaybackMode.name, mode.toString()).apply()
|
||||
}
|
||||
|
||||
enum class ThemePreference {
|
||||
LIGHT, DARK, BLACK, SYSTEM
|
||||
}
|
||||
|
|
|
@ -190,11 +190,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
val fileUri = FileProvider.getUriForFile(context!!.applicationContext, context.getString(R.string.provider_authority), output!!)
|
||||
showExportSuccessSnackbar(fileUri, exportType.contentType)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showTransportErrorDialog(e)
|
||||
} finally {
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Exception) { showTransportErrorDialog(e)
|
||||
} finally { progressDialog!!.dismiss() }
|
||||
}
|
||||
} else {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
|
@ -204,18 +201,15 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
withContext(Dispatchers.Main) {
|
||||
showExportSuccessSnackbar(output.uri, exportType.contentType)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
showTransportErrorDialog(e)
|
||||
} finally {
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Exception) { showTransportErrorDialog(e)
|
||||
} finally { progressDialog!!.dismiss() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportPreferences() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
backupPreferencesLauncher.launch(intent)
|
||||
}
|
||||
|
@ -240,7 +234,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
|
||||
private fun exportMediaFiles() {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
intent.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
backupMediaFilesLauncher.launch(intent)
|
||||
}
|
||||
|
@ -277,6 +271,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
builder.setNegativeButton(R.string.no, null)
|
||||
builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.setType("*/*")
|
||||
intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("application/octet-stream"))
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
|
@ -321,6 +316,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
builder.setNegativeButton(R.string.no, null)
|
||||
builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int ->
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
intent.setType("*/*")
|
||||
intent.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
restoreProgressLauncher.launch(intent)
|
||||
|
@ -331,25 +327,33 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
|
||||
private fun chooseProgressExportPathResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
exportWithWriter(EpisodesProgressWriter(), uri, Export.PROGRESS)
|
||||
}
|
||||
|
||||
private fun chooseOpmlExportPathResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
exportWithWriter(OpmlWriter(), uri, Export.OPML)
|
||||
}
|
||||
|
||||
private fun chooseHtmlExportPathResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
exportWithWriter(HtmlWriter(), uri, Export.HTML)
|
||||
}
|
||||
|
||||
private fun chooseFavoritesExportPathResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
exportWithWriter(FavoritesWriter(), uri, Export.FAVORITES)
|
||||
}
|
||||
|
||||
|
@ -357,6 +361,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
if (result.resultCode != RESULT_OK || result.data?.data == null) return
|
||||
val uri = result.data!!.data
|
||||
uri?.let {
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
if (isJsonFile(uri)) {
|
||||
progressDialog!!.show()
|
||||
lifecycleScope.launch {
|
||||
|
@ -371,9 +377,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
} else {
|
||||
val context = requireContext()
|
||||
|
@ -392,6 +396,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
if (result.resultCode != RESULT_OK || result.data == null) return
|
||||
val uri = result.data!!.data
|
||||
uri?.let {
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
if (isRealmFile(uri)) {
|
||||
progressDialog!!.show()
|
||||
lifecycleScope.launch {
|
||||
|
@ -403,9 +409,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
} else {
|
||||
val context = requireContext()
|
||||
|
@ -433,6 +437,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
private fun restorePreferencesResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data?.data == null) return
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
if (isPrefDir(uri)) {
|
||||
progressDialog!!.show()
|
||||
lifecycleScope.launch {
|
||||
|
@ -444,9 +450,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
} else {
|
||||
val context = requireContext()
|
||||
|
@ -458,6 +462,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
private fun restoreMediaFilesResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data?.data == null) return
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
if (isMediaFilesDir(uri)) {
|
||||
progressDialog!!.show()
|
||||
lifecycleScope.launch {
|
||||
|
@ -469,9 +475,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showImportSuccessDialog()
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
} else {
|
||||
val context = requireContext()
|
||||
|
@ -483,6 +487,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
private fun exportMediaFilesResult(result: ActivityResult) {
|
||||
if (result.resultCode != RESULT_OK || result.data?.data == null) return
|
||||
val uri = result.data!!.data!!
|
||||
// val takeFlags = result.data?.flags?.and(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) ?: 0
|
||||
// requireContext().contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||
progressDialog!!.show()
|
||||
lifecycleScope.launch {
|
||||
try {
|
||||
|
@ -493,9 +499,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showExportSuccessSnackbar(uri, null)
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,9 +515,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
showExportSuccessSnackbar(uri, "application/x-sqlite3")
|
||||
progressDialog!!.dismiss()
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
showTransportErrorDialog(e)
|
||||
}
|
||||
} catch (e: Throwable) { showTransportErrorDialog(e) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -537,9 +539,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
try {
|
||||
result.launch(intentPickAction)
|
||||
return
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(TAG, "No activity found. Should never happen...")
|
||||
}
|
||||
} catch (e: ActivityNotFoundException) { Log.e(TAG, "No activity found. Should never happen...") }
|
||||
|
||||
// If we are using a SDK lower than API 21 or the implicit intent failed
|
||||
// fallback to the legacy export process
|
||||
|
@ -578,8 +578,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
Logd(TAG, "feeds_: ${feeds_.size}")
|
||||
exportWriter.writeDocument(feeds_, writer, context)
|
||||
output
|
||||
} catch (e: IOException) {
|
||||
throw e
|
||||
} catch (e: IOException) { throw e
|
||||
} finally {
|
||||
if (writer != null) try { writer.close() } catch (e: IOException) { throw e }
|
||||
if (outputStream != null) try { outputStream.close() } catch (e: IOException) { throw e }
|
||||
|
@ -610,9 +609,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error during file export", e)
|
||||
null // return null in case of error
|
||||
} finally {
|
||||
writer?.close()
|
||||
}
|
||||
} finally { writer?.close() }
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
|
@ -637,9 +634,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
val destFile = exportSubDir.createFile("text/xml", file.name)
|
||||
if (destFile != null) copyFile(file, destFile, context)
|
||||
}
|
||||
} else {
|
||||
Log.e("Error", "shared_prefs directory not found")
|
||||
}
|
||||
} else Log.e("Error", "shared_prefs directory not found")
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
throw e
|
||||
|
@ -730,8 +725,8 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
object MediaFilesTransporter {
|
||||
private val TAG: String = MediaFilesTransporter::class.simpleName ?: "Anonymous"
|
||||
var feed: Feed? = null
|
||||
val nameFeedMap: MutableMap<String, Feed> = mutableMapOf()
|
||||
val nameEpisodeMap: MutableMap<String, Episode> = mutableMapOf()
|
||||
private val nameFeedMap: MutableMap<String, Feed> = mutableMapOf()
|
||||
private val nameEpisodeMap: MutableMap<String, Episode> = mutableMapOf()
|
||||
@Throws(IOException::class)
|
||||
fun exportToDocument(uri: Uri, context: Context) {
|
||||
try {
|
||||
|
@ -913,9 +908,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
} catch (e: IOException) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
throw e
|
||||
} finally {
|
||||
IOUtils.closeQuietly(inputStream)
|
||||
}
|
||||
} finally { IOUtils.closeQuietly(inputStream) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1124,6 +1117,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("EnumEntryName")
|
||||
private enum class IExport {
|
||||
prefOpmlExport,
|
||||
prefOpmlImport,
|
||||
|
|
|
@ -117,15 +117,15 @@ object AutoDownloads {
|
|||
if (allowedDLCount > 0) {
|
||||
var queryString = "feedId == ${f.id} AND isAutoDownloadEnabled == true AND media != nil AND media.downloaded == false"
|
||||
when (f.preferences?.autoDLPolicy) {
|
||||
FeedPreferences.AutoDLPolicy.ONLY_NEW -> {
|
||||
FeedPreferences.AutoDownloadPolicy.ONLY_NEW -> {
|
||||
queryString += " AND playState == -1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})"
|
||||
episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
|
||||
}
|
||||
FeedPreferences.AutoDLPolicy.NEWER -> {
|
||||
FeedPreferences.AutoDownloadPolicy.NEWER -> {
|
||||
queryString += " AND playState != 1 SORT(pubDate DESC) LIMIT(${3*allowedDLCount})"
|
||||
episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
|
||||
}
|
||||
FeedPreferences.AutoDLPolicy.OLDER -> {
|
||||
FeedPreferences.AutoDownloadPolicy.OLDER -> {
|
||||
queryString += " AND playState != 1 SORT(pubDate ASC) LIMIT(${3*allowedDLCount})"
|
||||
episodes = realm.query(Episode::class).query(queryString).find().toMutableList()
|
||||
}
|
||||
|
|
|
@ -211,10 +211,11 @@ object Feeds {
|
|||
Logd(TAG, "New feed has a higher page number.")
|
||||
savedFeed.nextPageLink = newFeed.nextPageLink
|
||||
}
|
||||
if (savedFeed.preferences != null && savedFeed.preferences!!.compareWithOther(newFeed.preferences)) {
|
||||
Logd(TAG, "Feed has updated preferences. Updating old feed's preferences")
|
||||
savedFeed.preferences!!.updateFromOther(newFeed.preferences)
|
||||
}
|
||||
// appears not useful
|
||||
// if (savedFeed.preferences != null && savedFeed.preferences!!.compareWithOther(newFeed.preferences)) {
|
||||
// Logd(TAG, "Feed has updated preferences. Updating old feed's preferences")
|
||||
// savedFeed.preferences!!.updateFromOther(newFeed.preferences)
|
||||
// }
|
||||
val priorMostRecent = savedFeed.mostRecentItem
|
||||
val priorMostRecentDate: Date? = priorMostRecent?.getPubDate()
|
||||
var idLong = Feed.newId()
|
||||
|
|
|
@ -18,7 +18,7 @@ import kotlin.coroutines.ContinuationInterceptor
|
|||
object RealmDB {
|
||||
private val TAG: String = RealmDB::class.simpleName ?: "Anonymous"
|
||||
|
||||
private const val SCHEMA_VERSION_NUMBER = 21L
|
||||
private const val SCHEMA_VERSION_NUMBER = 22L
|
||||
|
||||
private val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
|
|
|
@ -243,7 +243,6 @@ class Feed : RealmObject {
|
|||
if (imageUrl == null || imageUrl != other.imageUrl) return true
|
||||
}
|
||||
if (eigenTitle != other.eigenTitle) return true
|
||||
|
||||
if (other.identifier != null) {
|
||||
if (identifier == null || identifier != other.identifier) return true
|
||||
}
|
||||
|
@ -263,9 +262,7 @@ class Feed : RealmObject {
|
|||
if (paymentLinks.isEmpty() || paymentLinks != other.paymentLinks) return true
|
||||
}
|
||||
if (other.isPaged && !this.isPaged) return true
|
||||
|
||||
if (other.nextPageLink != this.nextPageLink) return true
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ package ac.mdiq.podcini.storage.model
|
|||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import io.realm.kotlin.ext.realmSetOf
|
||||
import io.realm.kotlin.types.EmbeddedRealmObject
|
||||
import io.realm.kotlin.types.RealmSet
|
||||
|
@ -26,7 +26,15 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
var username: String? = null
|
||||
var password: String? = null
|
||||
|
||||
var playAudioOnly: Boolean = false
|
||||
// var playAudioOnly: Boolean = false
|
||||
@Ignore
|
||||
var videoModePolicy: VideoMode = VideoMode.NONE
|
||||
get() = VideoMode.fromCode(videoMode)
|
||||
set(value) {
|
||||
field = value
|
||||
videoMode = field.code
|
||||
}
|
||||
var videoMode: Int = 0
|
||||
|
||||
var playSpeed: Float = SPEED_USE_GLOBAL
|
||||
|
||||
|
@ -118,22 +126,57 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
var countingPlayed: Boolean = true
|
||||
|
||||
@Ignore
|
||||
var autoDLPolicy: AutoDLPolicy = AutoDLPolicy.ONLY_NEW
|
||||
get() = AutoDLPolicy.fromCode(autoDLPolicyCode)
|
||||
var autoDLPolicy: AutoDownloadPolicy = AutoDownloadPolicy.ONLY_NEW
|
||||
get() = AutoDownloadPolicy.fromCode(autoDLPolicyCode)
|
||||
set(value) {
|
||||
field = value
|
||||
autoDLPolicyCode = value.code
|
||||
}
|
||||
var autoDLPolicyCode: Int = 0
|
||||
|
||||
enum class AutoDLPolicy(val code: Int, val resId: Int) {
|
||||
constructor() {}
|
||||
|
||||
constructor(feedID: Long, autoDownload: Boolean, autoDeleteAction: AutoDeleteAction,
|
||||
volumeAdaptionSetting: VolumeAdaptionSetting?, username: String?, password: String?) {
|
||||
this.feedID = feedID
|
||||
this.autoDownload = autoDownload
|
||||
this.autoDeleteAction = autoDeleteAction
|
||||
if (volumeAdaptionSetting != null) this.volumeAdaptionSetting = volumeAdaptionSetting
|
||||
this.username = username
|
||||
this.password = password
|
||||
this.autoDelete = autoDeleteAction.code
|
||||
this.volumeAdaption = volumeAdaptionSetting?.toInteger() ?: 0
|
||||
}
|
||||
|
||||
// These appear not needed
|
||||
/**
|
||||
* Compare another FeedPreferences with this one. .
|
||||
* @return True if the two objects are different.
|
||||
*/
|
||||
// fun compareWithOther(other: FeedPreferences?): Boolean {
|
||||
// if (other == null) return true
|
||||
// if (username != other.username) return true
|
||||
// if (password != other.password) return true
|
||||
// return false
|
||||
// }
|
||||
|
||||
/**
|
||||
* Update this FeedPreferences object from another one.
|
||||
*/
|
||||
// fun updateFromOther(other: FeedPreferences?) {
|
||||
// if (other == null) return
|
||||
// this.username = other.username
|
||||
// this.password = other.password
|
||||
// }
|
||||
|
||||
enum class AutoDownloadPolicy(val code: Int, val resId: Int) {
|
||||
ONLY_NEW(0, R.string.feed_auto_download_new),
|
||||
NEWER(1, R.string.feed_auto_download_newer),
|
||||
OLDER(2, R.string.feed_auto_download_older);
|
||||
|
||||
companion object {
|
||||
fun fromCode(code: Int): AutoDLPolicy {
|
||||
return enumValues<AutoDLPolicy>().firstOrNull { it.code == code } ?: ONLY_NEW
|
||||
fun fromCode(code: Int): AutoDownloadPolicy {
|
||||
return enumValues<AutoDownloadPolicy>().firstOrNull { it.code == code } ?: ONLY_NEW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -153,48 +196,11 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
}
|
||||
}
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(feedID: Long, autoDownload: Boolean, autoDeleteAction: AutoDeleteAction,
|
||||
volumeAdaptionSetting: VolumeAdaptionSetting?, username: String?, password: String?) {
|
||||
this.feedID = feedID
|
||||
this.autoDownload = autoDownload
|
||||
this.autoDeleteAction = autoDeleteAction
|
||||
if (volumeAdaptionSetting != null) this.volumeAdaptionSetting = volumeAdaptionSetting
|
||||
this.username = username
|
||||
this.password = password
|
||||
this.autoDelete = autoDeleteAction.code
|
||||
this.volumeAdaption = volumeAdaptionSetting?.toInteger() ?: 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare another FeedPreferences with this one. The feedID, autoDownload and AutoDeleteAction attribute are excluded from the
|
||||
* comparison.
|
||||
* @return True if the two objects are different.
|
||||
*/
|
||||
fun compareWithOther(other: FeedPreferences?): Boolean {
|
||||
if (other == null) return true
|
||||
if (username != other.username) return true
|
||||
if (password != other.password) return true
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Update this FeedPreferences object from another one. The feedID, autoDownload and AutoDeleteAction attributes are excluded
|
||||
* from the update.
|
||||
*/
|
||||
fun updateFromOther(other: FeedPreferences?) {
|
||||
if (other == null) return
|
||||
this.username = other.username
|
||||
this.password = other.password
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val SPEED_USE_GLOBAL: Float = -1f
|
||||
const val TAG_ROOT: String = "#root"
|
||||
const val TAG_SEPARATOR: String = "\u001e"
|
||||
|
||||
val FeedAutoDeleteOptions = AutoDeleteAction.values().map { it.tag }
|
||||
|
||||
val FeedAutoDeleteOptions = AutoDeleteAction.entries.map { it.tag }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package ac.mdiq.podcini.ui.actions.actionbutton
|
|||
|
||||
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.isCurrentlyPlaying
|
||||
import ac.mdiq.podcini.storage.model.Feed
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
@ -32,6 +34,13 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
|
|||
icon.setImageResource(getDrawable())
|
||||
}
|
||||
|
||||
protected fun playVideo(context: Context, media: Playable) {
|
||||
if (item.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY
|
||||
&& videoPlayMode != VideoMode.AUDIO_ONLY.code && videoMode != VideoMode.AUDIO_ONLY
|
||||
&& media.getMediaType() == MediaType.VIDEO)
|
||||
context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
}
|
||||
|
||||
@UnstableApi companion object {
|
||||
fun forItem(episode: Episode): EpisodeActionButton {
|
||||
val media = episode.media ?: return TTSActionButton(episode)
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package ac.mdiq.podcini.ui.actions.actionbutton
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.playback.base.InTheatre
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackService
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
|
@ -51,9 +46,11 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
EventFlow.postEvent(FlowEvent.PlayEvent(item))
|
||||
}
|
||||
|
||||
if (item.feed?.preferences?.playAudioOnly != true && videoPlayMode != VideoMode.AUDIO_ONLY.mode && videoMode != VideoMode.AUDIO_ONLY
|
||||
&& media.getMediaType() == MediaType.VIDEO)
|
||||
context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
// if (item.feed?.preferences?.videoModePolicy != FeedPreferences.VideomodePolicy.AUDIO_ONLY
|
||||
// && videoPlayMode != VideoMode.AUDIO_ONLY.mode && videoMode != VideoMode.AUDIO_ONLY
|
||||
// && media.getMediaType() == MediaType.VIDEO)
|
||||
// context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
playVideo(context, media)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package ac.mdiq.podcini.ui.actions.actionbutton
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.playback.base.InTheatre
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.playbackService
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
|
|
|
@ -2,18 +2,13 @@ package ac.mdiq.podcini.ui.actions.actionbutton
|
|||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileStreaming
|
||||
import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.preferences.UsageStatistics
|
||||
import ac.mdiq.podcini.preferences.UsageStatistics.logAction
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.storage.model.Playable
|
||||
import ac.mdiq.podcini.storage.model.RemoteMedia
|
||||
import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import android.content.Context
|
||||
|
@ -47,8 +42,11 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
.start()
|
||||
EventFlow.postEvent(FlowEvent.PlayEvent(item))
|
||||
|
||||
if (item.feed?.preferences?.playAudioOnly != true && videoPlayMode != VideoMode.AUDIO_ONLY.mode && videoMode != VideoMode.AUDIO_ONLY
|
||||
&& media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
// if (item.feed?.preferences?.videoModePolicy != FeedPreferences.VideomodePolicy.AUDIO_ONLY
|
||||
// && videoPlayMode != VideoMode.AUDIO_ONLY.mode && videoMode != VideoMode.AUDIO_ONLY
|
||||
// && media.getMediaType() == MediaType.VIDEO)
|
||||
// context.startActivity(getPlayerActivityIntent(context, MediaType.VIDEO))
|
||||
playVideo(context, media)
|
||||
}
|
||||
|
||||
class StreamingConfirmationDialog(private val context: Context, private val playable: Playable) {
|
||||
|
|
|
@ -5,14 +5,15 @@ import ac.mdiq.podcini.databinding.AudioControlsBinding
|
|||
import ac.mdiq.podcini.databinding.VideoEpisodeFragmentBinding
|
||||
import ac.mdiq.podcini.databinding.VideoplayerActivityBinding
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
|
||||
import ac.mdiq.podcini.playback.base.MediaPlayerBase
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curDurationFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curPositionFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curSpeedFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isCasting
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isPlayingVideoLocally
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isSleepTimerActive
|
||||
|
@ -36,17 +37,16 @@ import ac.mdiq.podcini.ui.fragment.ChaptersFragment
|
|||
import ac.mdiq.podcini.ui.utils.PictureInPictureUtil
|
||||
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
||||
import ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.IntentUtils.openInBrowser
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.ShareUtils.hasLinkToShare
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.media.AudioManager
|
||||
|
@ -95,18 +95,25 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
videoMode = (intent.getSerializableExtra(VIDEO_MODE) as? VideoMode) ?: VideoMode.None
|
||||
if (videoMode == VideoMode.None) {
|
||||
videoMode = VideoMode.entries.toTypedArray().getOrElse(videoPlayMode) { VideoMode.WINDOW_VIEW }
|
||||
if (videoMode == VideoMode.AUDIO_ONLY) {
|
||||
switchToAudioOnly = true
|
||||
finish()
|
||||
}
|
||||
if (videoMode != VideoMode.FULL_SCREEN_VIEW && videoMode != VideoMode.WINDOW_VIEW) {
|
||||
Logd(TAG, "videoMode not selected, use window mode")
|
||||
videoMode = VideoMode.WINDOW_VIEW
|
||||
}
|
||||
var vmCode = 0
|
||||
if (curMedia is EpisodeMedia) {
|
||||
val media_ = curMedia as EpisodeMedia
|
||||
val vPol = media_.episode?.feed?.preferences?.videoModePolicy
|
||||
if (vPol != null && vPol != VideoMode.NONE) vmCode = vPol.code
|
||||
}
|
||||
Logd(TAG, "onCreate vmCode: $vmCode")
|
||||
if (vmCode == 0) vmCode = videoPlayMode
|
||||
Logd(TAG, "onCreate vmCode: $vmCode")
|
||||
videoMode = VideoMode.entries.toTypedArray().getOrElse(vmCode) { VideoMode.WINDOW_VIEW }
|
||||
if (videoMode == VideoMode.AUDIO_ONLY) {
|
||||
switchToAudioOnly = true
|
||||
finish()
|
||||
}
|
||||
if (videoMode != VideoMode.FULL_SCREEN_VIEW && videoMode != VideoMode.WINDOW_VIEW) {
|
||||
Logd(TAG, "videoMode not selected, use window mode")
|
||||
videoMode = VideoMode.WINDOW_VIEW
|
||||
}
|
||||
|
||||
supportRequestWindowFeature(Window.FEATURE_ACTION_BAR_OVERLAY)
|
||||
setForVideoMode()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -393,13 +400,6 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
return super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
enum class VideoMode(val mode: Int) {
|
||||
None(0),
|
||||
WINDOW_VIEW(1),
|
||||
FULL_SCREEN_VIEW(2),
|
||||
AUDIO_ONLY(3)
|
||||
}
|
||||
|
||||
class PlaybackControlsDialog : DialogFragment() {
|
||||
private lateinit var dialog: AlertDialog
|
||||
private var _binding: AudioControlsBinding? = null
|
||||
|
@ -533,7 +533,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
Logd(TAG, "fragment onCreateView")
|
||||
_binding = VideoEpisodeFragmentBinding.inflate(LayoutInflater.from(activity))
|
||||
_binding = VideoEpisodeFragmentBinding.inflate(inflater)
|
||||
root = binding.root
|
||||
statusHandler = newStatusHandler()
|
||||
statusHandler!!.init()
|
||||
|
@ -646,17 +646,19 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
private fun setupVideoAspectRatio() {
|
||||
if (videoSurfaceCreated) {
|
||||
val windowMetrics = WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity as Activity)
|
||||
val videoWidth = if (videoMode == VideoMode.FULL_SCREEN_VIEW) max(windowMetrics.bounds.width(), windowMetrics.bounds.height())
|
||||
else min(windowMetrics.bounds.width(), windowMetrics.bounds.height())
|
||||
var videoHeight = 0
|
||||
val videoWidth = when (videoMode) {
|
||||
VideoMode.FULL_SCREEN_VIEW -> max(windowMetrics.bounds.width(), windowMetrics.bounds.height())
|
||||
VideoMode.WINDOW_VIEW -> min(windowMetrics.bounds.width(), windowMetrics.bounds.height())
|
||||
else -> min(windowMetrics.bounds.width(), windowMetrics.bounds.height())
|
||||
}
|
||||
val videoHeight: Int
|
||||
if (videoSize != null && videoSize!!.first > 0 && videoSize!!.second > 0) {
|
||||
Logd(TAG, "setupVideoAspectRatio Width,height of video: ${videoSize!!.first}, ${videoSize!!.second}")
|
||||
Logd(TAG, "setupVideoAspectRatio video width: ${videoSize!!.first} height: ${videoSize!!.second}")
|
||||
videoHeight = (videoWidth.toFloat() / videoSize!!.first * videoSize!!.second).toInt()
|
||||
Logd(TAG, "setupVideoAspectRatio Width,height of video 1: $videoWidth, $videoHeight")
|
||||
Logd(TAG, "setupVideoAspectRatio adjusted video width: $videoWidth height: $videoHeight")
|
||||
} else {
|
||||
Log.e(TAG, "setupVideoAspectRatio Could not determine video size")
|
||||
videoHeight = (videoWidth.toFloat() / 16 * 9).toInt()
|
||||
Logd(TAG, "setupVideoAspectRatio Width,height of video 2: $videoWidth, $videoHeight")
|
||||
Logd(TAG, "setupVideoAspectRatio Could not determine video size, use: $videoWidth $videoHeight")
|
||||
}
|
||||
val lp = binding.videoView.layoutParams
|
||||
lp.width = videoWidth
|
||||
|
@ -924,9 +926,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
|
||||
companion object {
|
||||
private val TAG: String = VideoplayerActivity::class.simpleName ?: "Anonymous"
|
||||
const val VIDEO_MODE = "Video_Mode"
|
||||
|
||||
var videoMode = VideoMode.None
|
||||
var videoMode = VideoMode.NONE
|
||||
|
||||
private val audioTracks: List<String>
|
||||
get() {
|
||||
|
|
|
@ -2,8 +2,6 @@ package ac.mdiq.podcini.ui.activity.starter
|
|||
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.VIDEO_MODE
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -14,7 +12,7 @@ import androidx.media3.common.util.UnstableApi
|
|||
* Launches the video player activity of the app with specific arguments.
|
||||
* Does not require a dependency on the actual implementation of the activity.
|
||||
*/
|
||||
@OptIn(UnstableApi::class) class VideoPlayerActivityStarter(private val context: Context, mode: VideoMode = VideoMode.None) {
|
||||
@OptIn(UnstableApi::class) class VideoPlayerActivityStarter(private val context: Context) {
|
||||
val intent: Intent = Intent(INTENT)
|
||||
val pendingIntent: PendingIntent
|
||||
get() = PendingIntent.getActivity(context, R.id.pending_intent_video_player, intent,
|
||||
|
@ -23,7 +21,6 @@ import androidx.media3.common.util.UnstableApi
|
|||
init {
|
||||
intent.setPackage(context.packageName)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
if (mode != VideoMode.None) intent.putExtra(VIDEO_MODE, mode)
|
||||
}
|
||||
|
||||
fun start() {
|
||||
|
|
|
@ -5,17 +5,18 @@ import ac.mdiq.podcini.databinding.AudioplayerFragmentBinding
|
|||
import ac.mdiq.podcini.databinding.PlayerUiFragmentBinding
|
||||
import ac.mdiq.podcini.playback.PlaybackServiceStarter
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler
|
||||
import ac.mdiq.podcini.playback.ServiceStatusHandler.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
|
||||
import ac.mdiq.podcini.playback.base.MediaPlayerBase
|
||||
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.getCurrentPlaybackSpeed
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.playback.cast.CastEnabledActivity
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curDurationFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curPositionFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.curSpeedFB
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.toggleFallbackSpeed
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isPlayingVideoLocally
|
||||
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isSleepTimerActive
|
||||
|
@ -26,16 +27,12 @@ import ac.mdiq.podcini.preferences.UserPreferences
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
|
||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||
import ac.mdiq.podcini.storage.model.Chapter
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.Playable
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.utils.ChapterUtils
|
||||
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.ui.actions.handler.EpisodeMenuHandler
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.VideoMode
|
||||
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
|
||||
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
|
||||
|
@ -475,7 +472,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
}
|
||||
R.id.show_video -> {
|
||||
playPause()
|
||||
VideoPlayerActivityStarter(requireContext(), VideoMode.FULL_SCREEN_VIEW).start()
|
||||
VideoPlayerActivityStarter(requireContext()).start()
|
||||
}
|
||||
R.id.disable_sleeptimer_item, R.id.set_sleeptimer_item -> SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
|
||||
R.id.open_feed_item -> {
|
||||
|
@ -550,8 +547,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
val media = curMedia
|
||||
if (media != null) {
|
||||
val mediaType = media.getMediaType()
|
||||
if (mediaType == MediaType.AUDIO || videoPlayMode == VideoMode.AUDIO_ONLY.mode || videoMode == VideoMode.AUDIO_ONLY
|
||||
|| (media is EpisodeMedia && media.episode?.feed?.preferences?.playAudioOnly == true)) {
|
||||
if (mediaType == MediaType.AUDIO || videoPlayMode == VideoMode.AUDIO_ONLY.code || videoMode == VideoMode.AUDIO_ONLY
|
||||
|| (media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy == VideoMode.AUDIO_ONLY)) {
|
||||
ensureService()
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||
} else {
|
||||
|
@ -577,7 +574,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
|
|||
if (curMedia != null) {
|
||||
val media = curMedia!!
|
||||
if (media.getMediaType() == MediaType.VIDEO && MediaPlayerBase.status != PlayerStatus.PLAYING &&
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.playAudioOnly != true)) {
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
|
||||
playPause()
|
||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
|
||||
} else playPause()
|
||||
|
|
|
@ -5,14 +5,15 @@ import ac.mdiq.podcini.databinding.AutodownloadFilterDialogBinding
|
|||
import ac.mdiq.podcini.databinding.FeedsettingsBinding
|
||||
import ac.mdiq.podcini.databinding.PlaybackSpeedFeedSettingDialogBinding
|
||||
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
|
||||
import ac.mdiq.podcini.playback.base.VideoMode
|
||||
import ac.mdiq.podcini.playback.base.VideoMode.Companion.videoModeTags
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
|
||||
import ac.mdiq.podcini.storage.database.Feeds.persistFeedPreferences
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDLPolicy
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.*
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.FeedAutoDeleteOptions
|
||||
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
|
@ -64,7 +65,9 @@ class FeedSettingsFragment : Fragment() {
|
|||
private var feed: Feed? = null
|
||||
private var autoDeleteSummaryResId by mutableIntStateOf(R.string.global_default)
|
||||
private var curPrefQueue by mutableStateOf(feed?.preferences?.queueTextExt ?: "Default")
|
||||
private var autoDeletePolicy = "global"
|
||||
private var autoDeletePolicy = AutoDeleteAction.GLOBAL.name
|
||||
private var videoModeSummaryResId by mutableIntStateOf(R.string.global_default)
|
||||
private var videoMode = VideoMode.NONE.name
|
||||
private var queues: List<PlayQueue>? = null
|
||||
|
||||
private var notificationPermissionDenied: Boolean = false
|
||||
|
@ -88,6 +91,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
val toolbar = binding.toolbar
|
||||
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
|
||||
|
||||
getVideoModePolicy()
|
||||
getAutoDeletePolicy()
|
||||
|
||||
binding.composeView.setContent {
|
||||
|
@ -130,30 +134,27 @@ class FeedSettingsFragment : Fragment() {
|
|||
// prefer play audio only
|
||||
Column {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Icon(ImageVector.vectorResource(id = R.drawable.baseline_audiotrack_24),
|
||||
"",
|
||||
tint = textColor)
|
||||
Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor)
|
||||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Text(
|
||||
text = stringResource(R.string.pref_audio_only_title),
|
||||
text = stringResource(R.string.feed_video_mode_label),
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = textColor
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.playAudioOnly ?: false) }
|
||||
Switch(
|
||||
checked = checked,
|
||||
modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
feed = upsertBlk(feed!!) { f ->
|
||||
f.preferences?.playAudioOnly = checked
|
||||
color = textColor,
|
||||
modifier = Modifier.clickable(onClick = {
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
VideoModeDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(view as? ViewGroup)?.addView(composeView)
|
||||
})
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = stringResource(R.string.pref_audio_only_sum),
|
||||
text = stringResource(videoModeSummaryResId),
|
||||
style = MaterialTheme.typography.body2,
|
||||
color = textColor
|
||||
)
|
||||
|
@ -270,8 +271,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
setContent {
|
||||
val showDialog = remember { mutableStateOf(true) }
|
||||
CustomTheme(requireContext()) {
|
||||
AutoDeleteDialog(showDialog.value,
|
||||
onDismissRequest = { showDialog.value = false })
|
||||
AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -596,19 +596,94 @@ class FeedSettingsFragment : Fragment() {
|
|||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun getVideoModePolicy() {
|
||||
when (feed?.preferences!!.videoModePolicy) {
|
||||
VideoMode.NONE -> {
|
||||
videoModeSummaryResId = R.string.global_default
|
||||
videoMode = VideoMode.NONE.tag
|
||||
}
|
||||
VideoMode.WINDOW_VIEW -> {
|
||||
videoModeSummaryResId = R.string.feed_video_mode_window
|
||||
videoMode = VideoMode.WINDOW_VIEW.tag
|
||||
}
|
||||
VideoMode.FULL_SCREEN_VIEW -> {
|
||||
videoModeSummaryResId = R.string.feed_video_mode_fullscreen
|
||||
videoMode = VideoMode.FULL_SCREEN_VIEW.tag
|
||||
}
|
||||
VideoMode.AUDIO_ONLY -> {
|
||||
videoModeSummaryResId = R.string.feed_video_mode_audioonly
|
||||
videoMode = VideoMode.AUDIO_ONLY.tag
|
||||
}
|
||||
}
|
||||
}
|
||||
@Composable
|
||||
fun VideoModeDialog(showDialog: Boolean, onDismissRequest: () -> Unit) {
|
||||
if (showDialog) {
|
||||
val (selectedOption, onOptionSelected) = remember { mutableStateOf(videoMode) }
|
||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(align = Alignment.Center)
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Column {
|
||||
videoModeTags.forEach { text ->
|
||||
Row(Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(checked = (text == selectedOption),
|
||||
onCheckedChange = {
|
||||
Logd(TAG, "row clicked: $text $selectedOption")
|
||||
if (text != selectedOption) {
|
||||
onOptionSelected(text)
|
||||
val mode_ = when (text) {
|
||||
VideoMode.NONE.tag -> VideoMode.NONE
|
||||
VideoMode.WINDOW_VIEW.tag -> VideoMode.WINDOW_VIEW
|
||||
VideoMode.FULL_SCREEN_VIEW.tag -> VideoMode.FULL_SCREEN_VIEW
|
||||
VideoMode.AUDIO_ONLY.tag -> VideoMode.AUDIO_ONLY
|
||||
else -> VideoMode.NONE
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.videoModePolicy = mode_ }
|
||||
getVideoModePolicy()
|
||||
onDismissRequest()
|
||||
}
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = text,
|
||||
style = MaterialTheme.typography.body1.merge(),
|
||||
// color = textColor,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAutoDeletePolicy() {
|
||||
when (feed?.preferences!!.autoDeleteAction) {
|
||||
AutoDeleteAction.GLOBAL -> {
|
||||
autoDeleteSummaryResId = R.string.global_default
|
||||
autoDeletePolicy = "global"
|
||||
autoDeletePolicy = AutoDeleteAction.GLOBAL.tag
|
||||
}
|
||||
AutoDeleteAction.ALWAYS -> {
|
||||
autoDeleteSummaryResId = R.string.feed_auto_download_always
|
||||
autoDeletePolicy = "always"
|
||||
autoDeletePolicy = AutoDeleteAction.ALWAYS.tag
|
||||
}
|
||||
AutoDeleteAction.NEVER -> {
|
||||
autoDeleteSummaryResId = R.string.feed_auto_download_never
|
||||
autoDeletePolicy = "never"
|
||||
autoDeletePolicy = AutoDeleteAction.NEVER.tag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -640,14 +715,12 @@ class FeedSettingsFragment : Fragment() {
|
|||
if (text != selectedOption) {
|
||||
onOptionSelected(text)
|
||||
val action_ = when (text) {
|
||||
"global" -> AutoDeleteAction.GLOBAL
|
||||
"always" -> AutoDeleteAction.ALWAYS
|
||||
"never" -> AutoDeleteAction.NEVER
|
||||
AutoDeleteAction.GLOBAL.tag -> AutoDeleteAction.GLOBAL
|
||||
AutoDeleteAction.ALWAYS.tag -> AutoDeleteAction.ALWAYS
|
||||
AutoDeleteAction.NEVER.tag -> AutoDeleteAction.NEVER
|
||||
else -> AutoDeleteAction.GLOBAL
|
||||
}
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.autoDeleteAction = action_
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.autoDeleteAction = action_ }
|
||||
getAutoDeletePolicy()
|
||||
onDismissRequest()
|
||||
}
|
||||
|
@ -720,7 +793,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
@Composable
|
||||
fun AutoDownloadPolicyDialog(showDialog: Boolean, onDismissRequest: () -> Unit) {
|
||||
if (showDialog) {
|
||||
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.autoDLPolicy ?: AutoDLPolicy.ONLY_NEW) }
|
||||
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.autoDLPolicy ?: AutoDownloadPolicy.ONLY_NEW) }
|
||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
|
@ -733,7 +806,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Column {
|
||||
AutoDLPolicy.entries.forEach { item ->
|
||||
AutoDownloadPolicy.entries.forEach { item ->
|
||||
Row(Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
|
|
@ -67,7 +67,6 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
|
|||
else IntentUtils.openInBrowser(context, url)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
super.onPageFinished(view, url)
|
||||
Logd(TAG, "Page finished")
|
||||
|
@ -88,9 +87,8 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
|
|||
HitTestResult.EMAIL_TYPE -> {
|
||||
Logd(TAG, "E-Mail of webview was long-pressed. Extra: " + r.extra)
|
||||
ContextCompat.getSystemService(context, ClipboardManager::class.java)?.setPrimaryClip(ClipData.newPlainText("Podcini", r.extra))
|
||||
if (Build.VERSION.SDK_INT <= 32 && this.context is MainActivity) {
|
||||
if (Build.VERSION.SDK_INT <= 32 && this.context is MainActivity)
|
||||
(this.context as MainActivity).showSnackbarAbovePlayer(resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
return true
|
||||
}
|
||||
else -> {
|
||||
|
|
|
@ -142,6 +142,11 @@
|
|||
<string name="feed_auto_download_always">Always</string>
|
||||
<string name="feed_auto_download_never">Never</string>
|
||||
|
||||
<string name="feed_video_mode_label">Video mode</string>
|
||||
<string name="feed_video_mode_window">Window mode</string>
|
||||
<string name="feed_video_mode_fullscreen">Full screen</string>
|
||||
<string name="feed_video_mode_audioonly">Audio only</string>
|
||||
|
||||
<string name="feed_auto_download_policy">Auto download policy</string>
|
||||
<string name="feed_auto_download_new">Only new items</string>
|
||||
<string name="feed_auto_download_newer">Newest unplayed</string>
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# 6.5.6
|
||||
|
||||
* in feed preferences, the setting "play audio only" for video feed is replaced with the setting of a video mode. If you set the previous setting, you need to redo with the new setting.
|
||||
* added some extra permission requests when exporting/importing various files, maybe needed in some system
|
||||
* re-enabed use of http traffic to work with relevant podcasts
|
||||
|
||||
# 6.5.5
|
||||
|
||||
* corrected issue of Youtube channel being set for auto-download when subscribing
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Version 6.5.6 brings several changes:
|
||||
|
||||
* in feed preferences, the setting "play audio only" for video feed is replaced with the setting of a video mode. If you set the previous setting, you need to redo with the new setting.
|
||||
* added some extra permission requests when exporting/importing various files, maybe needed in some system
|
||||
* re-enabed use of http traffic to work with relevant podcasts
|
Loading…
Reference in New Issue