6.13.11 commit
This commit is contained in:
parent
8eb74458bd
commit
5598ad630f
|
@ -26,8 +26,8 @@ android {
|
|||
vectorDrawables.useSupportLibrary false
|
||||
vectorDrawables.generatedDensities = []
|
||||
|
||||
versionCode 3020297
|
||||
versionName "6.13.10"
|
||||
versionCode 3020298
|
||||
versionName "6.13.11"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
|
|
@ -114,10 +114,10 @@
|
|||
website="https://github.com/ByteHamster/SearchPreference"
|
||||
license="MIT"
|
||||
licenseText="LICENSE_SEARCHPREFERENCE.txt" />
|
||||
<library
|
||||
name="Triangle Label View"
|
||||
author="Shota Saito"
|
||||
website="https://github.com/shts/TriangleLabelView"
|
||||
license="Apache 2.0"
|
||||
licenseText="LICENSE_TRIANGLE_LABEL_VIEW.txt" />
|
||||
<!-- <library-->
|
||||
<!-- name="Triangle Label View"-->
|
||||
<!-- author="Shota Saito"-->
|
||||
<!-- website="https://github.com/shts/TriangleLabelView"-->
|
||||
<!-- license="Apache 2.0"-->
|
||||
<!-- licenseText="LICENSE_TRIANGLE_LABEL_VIEW.txt" />-->
|
||||
</libraries>
|
||||
|
|
|
@ -15,13 +15,12 @@ import ac.mdiq.podcini.storage.database.LogsAndStats
|
|||
import ac.mdiq.podcini.storage.database.Queues
|
||||
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
|
||||
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.DownloadResult
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia.MediaMetadataRetrieverCompat
|
||||
import ac.mdiq.podcini.storage.utils.ChapterUtils
|
||||
import ac.mdiq.podcini.storage.utils.MediaMetadataRetrieverCompat
|
||||
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
||||
import ac.mdiq.podcini.ui.utils.NotificationUtils
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
|
@ -314,7 +313,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
|
|||
MediaMetadataRetrieverCompat().use { mmr ->
|
||||
if (it.media != null) mmr.setDataSource(it.media!!.fileUrl)
|
||||
durationStr = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
|
||||
if (durationStr != null) it.media?.setDuration(durationStr!!.toInt())
|
||||
if (durationStr != null) it.media?.setDuration(durationStr.toInt())
|
||||
}
|
||||
} catch (e: NumberFormatException) { Logd(TAG, "Invalid file duration: $durationStr")
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -344,9 +344,7 @@ class FeedBuilder(val context: Context, val showError: (String?, String)->Unit)
|
|||
}
|
||||
|
||||
fun subscribe(feed: Feed) {
|
||||
while (feed.isBuilding) {
|
||||
runBlocking { delay(200) }
|
||||
}
|
||||
while (feed.isBuilding) runBlocking { delay(200) }
|
||||
feed.id = 0L
|
||||
for (item in feed.episodes) {
|
||||
item.id = 0L
|
||||
|
|
|
@ -11,7 +11,7 @@ import ac.mdiq.podcini.net.feed.parser.utils.MimeTypeUtils
|
|||
import ac.mdiq.podcini.storage.database.Feeds
|
||||
import ac.mdiq.podcini.storage.database.LogsAndStats
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.utils.MediaMetadataRetrieverCompat
|
||||
import ac.mdiq.podcini.storage.model.EpisodeMedia.MediaMetadataRetrieverCompat
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.Context
|
||||
import android.media.MediaMetadataRetriever
|
||||
|
|
|
@ -193,7 +193,7 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
if (curMedia is EpisodeMedia) {
|
||||
val media_ = curMedia as EpisodeMedia
|
||||
var item = media_.episodeOrFetch()
|
||||
if (item != null && item.playState < PlayState.INPROGRESS.code) item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item!!, false) }
|
||||
if (item != null && item.playState < PlayState.INPROGRESS.code) item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item, false) }
|
||||
val eList = if (item?.feed?.preferences?.queue != null) curQueue.episodes else item?.feed?.getVirtualQueueItems() ?: listOf()
|
||||
curIndexInQueue = EpisodeUtil.indexOfItemWithId(eList, media_.id)
|
||||
} else curIndexInQueue = -1
|
||||
|
@ -629,8 +629,8 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
var exoPlayer: ExoPlayer? = null
|
||||
|
||||
private var exoplayerListener: Listener? = null
|
||||
private var audioSeekCompleteListener: java.lang.Runnable? = null
|
||||
private var audioCompletionListener: java.lang.Runnable? = null
|
||||
private var audioSeekCompleteListener: Runnable? = null
|
||||
private var audioCompletionListener: Runnable? = null
|
||||
private var audioErrorListener: Consumer<String>? = null
|
||||
private var bufferingUpdateListener: Consumer<Int>? = null
|
||||
private var loudnessEnhancer: LoudnessEnhancer? = null
|
||||
|
|
|
@ -41,7 +41,6 @@ import androidx.media3.extractor.DefaultExtractorsFactory
|
|||
import androidx.media3.extractor.mp3.Mp3Extractor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.concurrent.Volatile
|
||||
import kotlin.math.max
|
||||
|
||||
/*
|
||||
|
|
|
@ -92,7 +92,6 @@ import androidx.media3.common.MediaItem
|
|||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.Player.STATE_ENDED
|
||||
import androidx.media3.common.Player.STATE_IDLE
|
||||
|
||||
import androidx.media3.session.*
|
||||
import androidx.work.impl.utils.futures.SettableFuture
|
||||
import com.google.common.collect.ImmutableList
|
||||
|
@ -104,7 +103,6 @@ import java.util.*
|
|||
import java.util.concurrent.ScheduledFuture
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.concurrent.Volatile
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
@ -309,8 +307,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
else -> {}
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.N)
|
||||
TileService.requestListeningState(applicationContext, ComponentName(applicationContext, QuickSettingsTileService::class.java))
|
||||
TileService.requestListeningState(applicationContext, ComponentName(applicationContext, QuickSettingsTileService::class.java))
|
||||
|
||||
sendLocalBroadcast(applicationContext, ACTION_PLAYER_STATUS_CHANGED)
|
||||
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED)
|
||||
|
@ -359,9 +356,38 @@ class PlaybackService : MediaLibraryService() {
|
|||
if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) {
|
||||
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
|
||||
// only mark the item as played if we're not keeping it anyways
|
||||
item = setPlayStateSync(PlayState.PLAYED.code, item!!, ended || (skipped && smartMarkAsPlayed), false)
|
||||
if (playable is EpisodeMedia && (ended || skipped || playingNext)) {
|
||||
item = upsert(item!!) { it.media?.playbackCompletionDate = Date() }
|
||||
|
||||
// item = setPlayStateSync(PlayState.PLAYED.code, item!!, ended || (skipped && smartMarkAsPlayed), false)
|
||||
// if (playable is EpisodeMedia && (ended || skipped || playingNext)) {
|
||||
// item = upsert(item!!) {
|
||||
// it.media?.playbackCompletionDate = Date()
|
||||
// }
|
||||
// EventFlow.postEvent(FlowEvent.HistoryEvent())
|
||||
// }
|
||||
|
||||
if (playable !is EpisodeMedia)
|
||||
item = setPlayStateSync(PlayState.PLAYED.code, item!!, ended || (skipped && smartMarkAsPlayed), false)
|
||||
else {
|
||||
val item_ = realm.query(Episode::class).query("id == $0", item!!.id).first().find()
|
||||
if (item_ != null) {
|
||||
item = upsert(item_) {
|
||||
it.playState = PlayState.PLAYED.code
|
||||
val media = it.media
|
||||
if (media != null) {
|
||||
media.startPosition = playable.startPosition
|
||||
media.startTime = playable.startTime
|
||||
media.playedDurationWhenStarted = playable.playedDurationWhenStarted
|
||||
media.setPosition(playable.getPosition())
|
||||
media.setLastPlayedTime(System.currentTimeMillis())
|
||||
if (media.startPosition >= 0 && media.getPosition() > media.startPosition)
|
||||
media.playedDuration = (media.playedDurationWhenStarted + media.getPosition() - media.startPosition)
|
||||
media.timeSpent = media.timeSpentOnStart + (System.currentTimeMillis() - media.startTime).toInt()
|
||||
if (ended || (skipped && smartMarkAsPlayed)) media.setPosition(0)
|
||||
if (ended || skipped || playingNext) media.playbackCompletionDate = Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(item))
|
||||
EventFlow.postEvent(FlowEvent.HistoryEvent())
|
||||
}
|
||||
val action = item?.feed?.preferences?.autoDeleteAction
|
||||
|
@ -370,8 +396,8 @@ class PlaybackService : MediaLibraryService() {
|
|||
val isItemdeletable = (!shouldKeepSuperEpisode || (item?.isSUPER != true && item?.playState != PlayState.AGAIN.code && item?.playState != PlayState.FOREVER.code))
|
||||
if (playable is EpisodeMedia && shouldAutoDelete && isItemdeletable) {
|
||||
if (playable.localFileAvailable()) item = deleteMediaSync(this@PlaybackService, item!!)
|
||||
if (prefDeleteRemovesFromQueue) removeFromAllQueuesSync( item!!)
|
||||
} else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item!!)
|
||||
if (prefDeleteRemovesFromQueue) removeFromAllQueuesSync(item)
|
||||
} else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -674,7 +700,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
recreateMediaPlayer()
|
||||
if (LocalMediaPlayer.exoPlayer == null) LocalMediaPlayer.createStaticPlayer(applicationContext)
|
||||
val intent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, intent, if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else FLAG_UPDATE_CURRENT)
|
||||
val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_IMMUTABLE)
|
||||
mediaSession = MediaLibrarySession.Builder(applicationContext, LocalMediaPlayer.exoPlayer!!, mediaLibrarySessionCK)
|
||||
.setId(packageName)
|
||||
.setSessionActivity(pendingIntent)
|
||||
|
@ -757,7 +783,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val keycode = intent?.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1) ?: -1
|
||||
val customAction = intent?.getStringExtra(MediaButtonReceiver.EXTRA_CUSTOM_ACTION)
|
||||
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) ?: false
|
||||
val hardwareButton = intent?.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false) == true
|
||||
val keyEvent: KeyEvent? = if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU)
|
||||
intent?.getParcelableExtra(EXTRA_KEY_EVENT, KeyEvent::class.java)
|
||||
else {
|
||||
|
@ -793,8 +819,8 @@ class PlaybackService : MediaLibraryService() {
|
|||
playable != null -> {
|
||||
recreateMediaSessionIfNeeded()
|
||||
Logd(TAG, "onStartCommand status: $status")
|
||||
val allowStreamThisTime = intent?.getBooleanExtra(EXTRA_ALLOW_STREAM_THIS_TIME, false) ?: false
|
||||
val allowStreamAlways = intent?.getBooleanExtra(EXTRA_ALLOW_STREAM_ALWAYS, false) ?: false
|
||||
val allowStreamThisTime = intent?.getBooleanExtra(EXTRA_ALLOW_STREAM_THIS_TIME, false) == true
|
||||
val allowStreamAlways = intent?.getBooleanExtra(EXTRA_ALLOW_STREAM_ALWAYS, false) == true
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0)
|
||||
if (allowStreamAlways) isAllowMobileStreaming = true
|
||||
startPlaying(allowStreamThisTime)
|
||||
|
@ -849,8 +875,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
val pendingIntentAlwaysAllow =
|
||||
if (Build.VERSION.SDK_INT >= VERSION_CODES.O) PendingIntent.getForegroundService(this, R.id.pending_intent_allow_stream_always,
|
||||
intentAlwaysAllow, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
else PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow,
|
||||
FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
else PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
|
||||
|
||||
val builder = Notification.Builder(this, NotificationUtils.CHANNEL_ID.user_action.name)
|
||||
.setSmallIcon(R.drawable.ic_notification_stream)
|
||||
|
@ -863,7 +888,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
.addAction(R.drawable.ic_notification_stream, getString(R.string.confirm_mobile_streaming_button_always), pendingIntentAlwaysAllow)
|
||||
.setAutoCancel(true)
|
||||
|
||||
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(5566, builder.build())
|
||||
}
|
||||
|
||||
|
@ -941,7 +966,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
return true
|
||||
}
|
||||
}
|
||||
KeyEvent.KEYCODE_MEDIA_STOP -> {
|
||||
KEYCODE_MEDIA_STOP -> {
|
||||
if (status == PlayerStatus.FALLBACK || status == PlayerStatus.PLAYING) mPlayer?.pause(abandonFocus = true, reinit = true)
|
||||
return true
|
||||
}
|
||||
|
@ -1154,9 +1179,8 @@ class PlaybackService : MediaLibraryService() {
|
|||
media.setPosition(position)
|
||||
media.setLastPlayedTime(System.currentTimeMillis())
|
||||
if (it.isNew) it.playState = PlayState.UNPLAYED.code
|
||||
if (media.startPosition >= 0 && media.getPosition() > media.startPosition) {
|
||||
if (media.startPosition >= 0 && media.getPosition() > media.startPosition)
|
||||
media.playedDuration = (media.playedDurationWhenStarted + media.getPosition() - media.startPosition)
|
||||
}
|
||||
media.timeSpent = media.timeSpentOnStart + (System.currentTimeMillis() - media.startTime).toInt()
|
||||
Logd(TAG, "saveCurrentPosition ${media.startTime} timeSpent: ${media.timeSpent} playedDuration: ${media.playedDuration}")
|
||||
}
|
||||
|
@ -1246,7 +1270,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
.build(),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class CustomMediaNotificationProvider(context: Context) : DefaultMediaNotificationProvider(context) {
|
||||
override fun addNotificationActions(mediaSession: MediaSession, mediaButtons: ImmutableList<CommandButton>,
|
||||
|
@ -1283,7 +1306,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
* to notify the PlaybackService about updates from the running tasks.
|
||||
*/
|
||||
class TaskManager(private val context: Context, private val callback: PSTMCallback) {
|
||||
private val schedExecutor: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE) { r: java.lang.Runnable? ->
|
||||
private val schedExecutor: ScheduledThreadPoolExecutor = ScheduledThreadPoolExecutor(SCHED_EX_POOL_SIZE) { r: Runnable? ->
|
||||
val t = Thread(r)
|
||||
t.priority = Thread.MIN_PRIORITY
|
||||
t
|
||||
|
@ -1441,7 +1464,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
schedExecutor.shutdownNow()
|
||||
}
|
||||
|
||||
private fun useMainThreadIfNecessary(runnable: java.lang.Runnable): java.lang.Runnable {
|
||||
private fun useMainThreadIfNecessary(runnable: Runnable): Runnable {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
// Called in main thread => ExoPlayer is used
|
||||
// Run on ui thread even if called from schedExecutor
|
||||
|
@ -1453,7 +1476,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
/**
|
||||
* Sleeps for a given time and then pauses playback.
|
||||
*/
|
||||
internal inner class SleepTimer(private val waitingTime: Long) : java.lang.Runnable {
|
||||
internal inner class SleepTimer(private val waitingTime: Long) : Runnable {
|
||||
private var hasVibrated = false
|
||||
private var timeLeft = waitingTime
|
||||
private var shakeListener: ShakeListener? = null
|
||||
|
@ -1479,7 +1502,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
if (timeLeft < NOTIFICATION_THRESHOLD) {
|
||||
Logd(TAG, "Sleep timer is about to expire")
|
||||
if (SleepTimerPreferences.vibrate() && !hasVibrated) {
|
||||
val v = context.getSystemService(Context.VIBRATOR_SERVICE) as? Vibrator
|
||||
val v = context.getSystemService(VIBRATOR_SERVICE) as? Vibrator
|
||||
if (v != null) {
|
||||
v.vibrate(500)
|
||||
hasVibrated = true
|
||||
|
@ -1527,7 +1550,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
private fun resume() {
|
||||
// only a precaution, the user should actually not be able to activate shake to reset
|
||||
// when the accelerometer is not available
|
||||
mSensorMgr = mContext.getSystemService(Context.SENSOR_SERVICE) as SensorManager
|
||||
mSensorMgr = mContext.getSystemService(SENSOR_SERVICE) as SensorManager
|
||||
if (mSensorMgr == null) throw UnsupportedOperationException("Sensors not supported")
|
||||
|
||||
mAccelerometer = mSensorMgr!!.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)
|
||||
|
@ -1656,7 +1679,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
|
||||
var isStartWhenPrepared: Boolean
|
||||
get() = playbackService?.mPlayer?.startWhenPrepared?.get() ?: false
|
||||
get() = playbackService?.mPlayer?.startWhenPrepared?.get() == true
|
||||
set(s) {
|
||||
playbackService?.mPlayer?.startWhenPrepared?.set(s)
|
||||
}
|
||||
|
@ -1694,7 +1717,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
|
||||
fun isSleepTimerActive(): Boolean {
|
||||
return playbackService?.taskManager?.isSleepTimerActive ?: false
|
||||
return playbackService?.taskManager?.isSleepTimerActive == true
|
||||
}
|
||||
|
||||
fun clearCurTempSpeed() {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package ac.mdiq.podcini.playback.service
|
||||
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curState
|
||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver
|
||||
import ac.mdiq.podcini.storage.model.CurrentState.Companion.PLAYER_STATUS_PLAYING
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curState
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import android.view.KeyEvent
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
class QuickSettingsTileService : TileService() {
|
||||
override fun onTileAdded() {
|
||||
super.onTileAdded()
|
||||
|
|
|
@ -13,7 +13,6 @@ import ac.mdiq.podcini.playback.service.PlaybackService.Companion.ACTION_SHUTDOW
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.Prefs
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
|
||||
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync
|
||||
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsert
|
||||
|
@ -215,11 +214,7 @@ object Episodes {
|
|||
*/
|
||||
fun setPlayState(played: Int, resetMediaPosition: Boolean, vararg episodes: Episode) : Job {
|
||||
Logd(TAG, "setPlayState called")
|
||||
return runOnIOScope {
|
||||
for (episode in episodes) {
|
||||
setPlayStateSync(played, episode, resetMediaPosition)
|
||||
}
|
||||
}
|
||||
return runOnIOScope { for (episode in episodes) setPlayStateSync(played, episode, resetMediaPosition) }
|
||||
}
|
||||
|
||||
suspend fun setPlayStateSync(played: Int, episode: Episode, resetMediaPosition: Boolean, removeFromQueue: Boolean = true) : Episode {
|
||||
|
|
|
@ -285,7 +285,7 @@ object Feeds {
|
|||
else savedFeed.episodes.add(idx, episode)
|
||||
|
||||
val pubDate = episode.getPubDate()
|
||||
if (pubDate == null || priorMostRecentDate == null || priorMostRecentDate.before(pubDate) || priorMostRecentDate == pubDate) {
|
||||
if (priorMostRecentDate == null || priorMostRecentDate.before(pubDate) || priorMostRecentDate == pubDate) {
|
||||
Logd(TAG, "Marking episode published on $pubDate new, prior most recent date = $priorMostRecentDate")
|
||||
episode.playState = PlayState.NEW.code
|
||||
if (savedFeed.preferences?.autoAddNewToQueue == true) {
|
||||
|
@ -385,8 +385,7 @@ object Feeds {
|
|||
for (e in savedFeed.episodes) savedFeed.totleDuration += e.media?.duration ?: 0
|
||||
|
||||
val resultFeed = savedFeed
|
||||
try {
|
||||
upsertBlk(savedFeed) {}
|
||||
try { upsertBlk(savedFeed) {}
|
||||
} catch (e: InterruptedException) { e.printStackTrace()
|
||||
} catch (e: ExecutionException) { e.printStackTrace() }
|
||||
return resultFeed
|
||||
|
@ -536,7 +535,7 @@ object Feeds {
|
|||
feed.downloadUrl = null
|
||||
feed.hasVideoMedia = video
|
||||
feed.fileUrl = File(feedfilePath, getFeedfileName(feed)).toString()
|
||||
feed.preferences = FeedPreferences(feed.id, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "")
|
||||
feed.preferences = FeedPreferences(feed.id, false, AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "")
|
||||
feed.preferences!!.keepUpdated = false
|
||||
feed.preferences!!.queueId = -2L
|
||||
return feed
|
||||
|
@ -736,10 +735,10 @@ object Feeds {
|
|||
return string1 == string2
|
||||
}
|
||||
internal fun datesLookSimilar(item1: Episode, item2: Episode): Boolean {
|
||||
if (item1.getPubDate() == null || item2.getPubDate() == null) return false
|
||||
// if (item1.getPubDate() == null || item2.getPubDate() == null) return false
|
||||
val dateFormat = DateFormat.getDateInstance(DateFormat.SHORT, Locale.US) // MM/DD/YY
|
||||
val dateOriginal = dateFormat.format(item2.getPubDate()!!)
|
||||
val dateNew = dateFormat.format(item1.getPubDate()!!)
|
||||
val dateOriginal = dateFormat.format(item2.getPubDate())
|
||||
val dateNew = dateFormat.format(item1.getPubDate())
|
||||
return dateOriginal == dateNew // Same date; time is ignored.
|
||||
}
|
||||
internal fun durationsLookSimilar(media1: EpisodeMedia, media2: EpisodeMedia): Boolean {
|
||||
|
|
|
@ -4,7 +4,6 @@ import ac.mdiq.podcini.storage.database.RealmDB.realm
|
|||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsert
|
||||
import ac.mdiq.podcini.storage.model.DownloadResult
|
||||
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
|
@ -30,4 +29,11 @@ object LogsAndStats {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Compares the completion date of two DownloadResult objects. */
|
||||
class DownloadResultComparator : Comparator<DownloadResult> {
|
||||
override fun compare(lhs: DownloadResult, rhs: DownloadResult): Int {
|
||||
return rhs.getCompletionDate().compareTo(lhs.getCompletionDate())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -344,7 +344,7 @@ object Queues {
|
|||
val random = Random()
|
||||
return random.nextInt(queueItems.size + 1)
|
||||
}
|
||||
else -> throw AssertionError("calcPosition() : unrecognized enqueueLocation option: $enqueueLocation")
|
||||
// else -> throw AssertionError("calcPosition() : unrecognized enqueueLocation option: $enqueueLocation")
|
||||
}
|
||||
}
|
||||
private fun getPositionOfFirstNonDownloadingItem(startPosition: Int, queueItems: List<Episode>): Int {
|
||||
|
|
|
@ -4,10 +4,9 @@ import ac.mdiq.podcini.storage.database.RealmDB.realm
|
|||
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger
|
||||
import ac.mdiq.podcini.storage.utils.MediaMetadataRetrieverCompat
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.showStackTrace
|
||||
import android.content.Context
|
||||
import android.media.MediaMetadataRetriever
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
@ -18,6 +17,7 @@ import io.realm.kotlin.types.EmbeddedRealmObject
|
|||
import io.realm.kotlin.types.annotations.Ignore
|
||||
import io.realm.kotlin.types.annotations.Index
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
|
@ -229,7 +229,7 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
fun hasEmbeddedPicture(): Boolean {
|
||||
// TODO: checkEmbeddedPicture needs to update current copy
|
||||
if (hasEmbeddedPicture == null) checkEmbeddedPicture()
|
||||
return hasEmbeddedPicture ?: false
|
||||
return hasEmbeddedPicture == true
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
|
@ -411,6 +411,15 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* On SDK<29, this class does not have a close method yet, so the app crashes when using try-with-resources.
|
||||
*/
|
||||
class MediaMetadataRetrieverCompat : MediaMetadataRetriever(), AutoCloseable {
|
||||
override fun close() {
|
||||
try { release() } catch (e: IOException) { e.printStackTrace() }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = EpisodeMedia::class.simpleName ?: "Anonymous"
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package ac.mdiq.podcini.storage.utils
|
||||
|
||||
import ac.mdiq.podcini.storage.model.DownloadResult
|
||||
|
||||
/** Compares the completion date of two DownloadResult objects. */
|
||||
class DownloadResultComparator : Comparator<DownloadResult> {
|
||||
override fun compare(lhs: DownloadResult, rhs: DownloadResult): Int {
|
||||
return rhs.getCompletionDate().compareTo(lhs.getCompletionDate())
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package ac.mdiq.podcini.storage.utils
|
||||
|
||||
import android.media.MediaMetadataRetriever
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* On SDK<29, this class does not have a close method yet, so the app crashes when using try-with-resources.
|
||||
*/
|
||||
class MediaMetadataRetrieverCompat : MediaMetadataRetriever(), AutoCloseable {
|
||||
override fun close() {
|
||||
try {
|
||||
release()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,9 +19,9 @@ import ac.mdiq.podcini.storage.database.RealmDB
|
|||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.utils.AudioMediaTools
|
||||
import ac.mdiq.podcini.storage.utils.FilesUtils
|
||||
import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.deleteEpisodesWarnLocal
|
||||
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
|
||||
import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment
|
||||
import ac.mdiq.podcini.ui.utils.LocalDeleteModal
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.IntentUtils
|
||||
|
@ -86,7 +86,7 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
|
|||
val media = item.media ?: return TTSActionButton(item)
|
||||
val isDownloadingMedia = when (media.downloadUrl) {
|
||||
null -> false
|
||||
else -> DownloadServiceInterface.get()?.isDownloadingEpisode(media.downloadUrl!!)?:false
|
||||
else -> DownloadServiceInterface.get()?.isDownloadingEpisode(media.downloadUrl!!) == true
|
||||
}
|
||||
Logd("ItemActionButton", "forItem: local feed: ${item.feed?.isLocalFeed} downloaded: ${media.downloaded} playing: ${isCurrentlyPlaying(media)} ${item.title} ")
|
||||
return when {
|
||||
|
@ -292,7 +292,7 @@ class DeleteActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
}
|
||||
|
||||
override fun onClick(context: Context) {
|
||||
LocalDeleteModal.deleteEpisodesWarnLocal(context, listOf(item))
|
||||
deleteEpisodesWarnLocal(context, listOf(item))
|
||||
actionState.value = getLabel()
|
||||
}
|
||||
}
|
||||
|
@ -360,7 +360,7 @@ class DownloadActionButton(item: Episode) : EpisodeActionButton(item) {
|
|||
|
||||
private fun shouldNotDownload(media: EpisodeMedia?): Boolean {
|
||||
if (media?.downloadUrl == null) return true
|
||||
val isDownloading = DownloadServiceInterface.get()?.isDownloadingEpisode(media.downloadUrl!!)?:false
|
||||
val isDownloading = DownloadServiceInterface.get()?.isDownloadingEpisode(media.downloadUrl!!) == true
|
||||
return isDownloading || media.downloaded
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.actions
|
||||
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
|
||||
/**
|
||||
* Utilities for menu items
|
||||
*/
|
||||
object MenuItemUtils {
|
||||
/**
|
||||
* When pressing a context menu item, Android calls onContextItemSelected
|
||||
* for ALL fragments in arbitrary order, not just for the fragment that the
|
||||
* context menu was created from. This assigns the listener to every menu item,
|
||||
* so that the correct fragment is always called first and can consume the click.
|
||||
*
|
||||
* Note that Android still calls the onContextItemSelected methods of all fragments
|
||||
* when the passed listener returns false.
|
||||
*/
|
||||
fun setOnClickListeners(menu: Menu?, listener: MenuItem.OnMenuItemClickListener?) {
|
||||
for (i in 0 until menu!!.size()) {
|
||||
if (menu.getItem(i).subMenu != null) setOnClickListeners(menu.getItem(i).subMenu, listener)
|
||||
menu.getItem(i).setOnMenuItemClickListener(listener)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.actions
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.EpisodeFilter
|
||||
|
||||
interface SwipeAction {
|
||||
fun getId(): String?
|
||||
fun getTitle(context: Context): String
|
||||
|
||||
@DrawableRes
|
||||
fun getActionIcon(): Int
|
||||
|
||||
@AttrRes
|
||||
@DrawableRes
|
||||
fun getActionColor(): Int
|
||||
|
||||
fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter)
|
||||
|
||||
fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
enum class ActionTypes {
|
||||
NO_ACTION,
|
||||
COMBO,
|
||||
RATING,
|
||||
COMMENT,
|
||||
SET_PLAY_STATE,
|
||||
ADD_TO_QUEUE,
|
||||
PUT_TO_QUEUE,
|
||||
REMOVE_FROM_QUEUE,
|
||||
START_DOWNLOAD,
|
||||
DELETE,
|
||||
REMOVE_FROM_HISTORY,
|
||||
SHELVE,
|
||||
ERASE
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ package ac.mdiq.podcini.ui.actions
|
|||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
|
||||
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
|
||||
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
|
||||
import ac.mdiq.podcini.storage.database.Queues.addToQueue
|
||||
|
@ -14,19 +15,19 @@ import ac.mdiq.podcini.storage.model.Episode
|
|||
import ac.mdiq.podcini.storage.model.EpisodeFilter
|
||||
import ac.mdiq.podcini.storage.model.PlayState
|
||||
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
|
||||
import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes
|
||||
import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes.NO_ACTION
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.compose.*
|
||||
import ac.mdiq.podcini.ui.fragment.*
|
||||
import ac.mdiq.podcini.ui.utils.LocalDeleteModal.deleteEpisodesWarnLocal
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
@ -50,12 +51,31 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.*
|
||||
|
||||
open class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver {
|
||||
interface SwipeAction {
|
||||
fun getId(): String?
|
||||
fun getTitle(context: Context): String
|
||||
|
||||
@DrawableRes
|
||||
fun getActionIcon(): Int
|
||||
|
||||
@AttrRes
|
||||
@DrawableRes
|
||||
fun getActionColor(): Int
|
||||
|
||||
fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter)
|
||||
|
||||
fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver {
|
||||
|
||||
@set:JvmName("setFilterProperty")
|
||||
var filter: EpisodeFilter? = null
|
||||
|
@ -111,6 +131,22 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
}
|
||||
}
|
||||
|
||||
enum class ActionTypes {
|
||||
NO_ACTION,
|
||||
COMBO,
|
||||
RATING,
|
||||
COMMENT,
|
||||
SET_PLAY_STATE,
|
||||
ADD_TO_QUEUE,
|
||||
PUT_TO_QUEUE,
|
||||
REMOVE_FROM_QUEUE,
|
||||
START_DOWNLOAD,
|
||||
DELETE,
|
||||
REMOVE_FROM_HISTORY,
|
||||
SHELVE,
|
||||
ERASE
|
||||
}
|
||||
|
||||
class AddToQueueSwipeAction : SwipeAction {
|
||||
override fun getId(): String {
|
||||
return ActionTypes.ADD_TO_QUEUE.name
|
||||
|
@ -155,7 +191,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
Surface(shape = RoundedCornerShape(16.dp), border = BorderStroke(1.dp, Color.Yellow)) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
for (action in swipeActions) {
|
||||
if (action.getId() == NO_ACTION.name || action.getId() == ActionTypes.COMBO.name) continue
|
||||
if (action.getId() == ActionTypes.NO_ACTION.name || action.getId() == ActionTypes.COMBO.name) continue
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
|
||||
action.performAction(item, fragment, filter)
|
||||
showDialog = false
|
||||
|
@ -280,7 +316,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
|
||||
class NoActionSwipeAction : SwipeAction {
|
||||
override fun getId(): String {
|
||||
return NO_ACTION.name
|
||||
return ActionTypes.NO_ACTION.name
|
||||
}
|
||||
override fun getActionIcon(): Int {
|
||||
return R.drawable.ic_questionmark
|
||||
|
@ -569,7 +605,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
|
||||
@JvmStatic
|
||||
fun getPrefsWithDefaults(tag: String): Actions {
|
||||
val defaultActions = "${NO_ACTION.name},${NO_ACTION.name}"
|
||||
val defaultActions = "${ActionTypes.NO_ACTION.name},${ActionTypes.NO_ACTION.name}"
|
||||
return getPrefs(tag, defaultActions)
|
||||
}
|
||||
|
||||
|
@ -577,6 +613,25 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
// return prefs!!.getBoolean(KEY_PREFIX_NO_ACTION + tag, true)
|
||||
// }
|
||||
|
||||
fun deleteEpisodesWarnLocal(context: Context, items: Iterable<Episode>) {
|
||||
val localItems: MutableList<Episode> = mutableListOf()
|
||||
for (item in items) {
|
||||
if (item.feed?.isLocalFeed == true) localItems.add(item)
|
||||
else deleteEpisodeMedia(context, item)
|
||||
}
|
||||
|
||||
if (localItems.isNotEmpty()) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_episode_label)
|
||||
.setMessage(R.string.delete_local_feed_warning_body)
|
||||
.setPositiveButton(R.string.delete_label) { dialog: DialogInterface?, which: Int ->
|
||||
for (item in localItems) deleteEpisodeMedia(context, item)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun showSettingDialog(fragment: Fragment, tag: String) {
|
||||
val composeView = ComposeView(fragment.requireContext()).apply {
|
||||
setContent {
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.content.ClipboardManager
|
|||
import android.content.DialogInterface
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -26,9 +25,6 @@ import java.io.FileInputStream
|
|||
import java.io.IOException
|
||||
import java.nio.charset.Charset
|
||||
|
||||
/**
|
||||
* Displays the 'crash report' screen
|
||||
*/
|
||||
class BugReportActivity : AppCompatActivity() {
|
||||
private var _binding: BugReportBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
|
|
@ -92,10 +92,6 @@ import kotlinx.coroutines.*
|
|||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* The activity that is shown when the user launches the app.
|
||||
*/
|
||||
|
||||
class MainActivity : CastEnabledActivity() {
|
||||
private var drawerLayout: DrawerLayout? = null
|
||||
|
||||
|
@ -119,7 +115,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
private var lastTheme = 0
|
||||
private var navigationBarInsets = Insets.NONE
|
||||
|
||||
val prefs by lazy { getSharedPreferences(PREF_NAME, MODE_PRIVATE) }
|
||||
val prefs by lazy { getSharedPreferences("MainActivityPrefs", MODE_PRIVATE) }
|
||||
|
||||
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
||||
Toast.makeText(this, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
|
||||
|
@ -129,54 +125,11 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.notification_permission_text)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int ->
|
||||
checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> checkAndRequestUnrestrictedBackgroundActivity(this) }
|
||||
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int -> checkAndRequestUnrestrictedBackgroundActivity(this) }
|
||||
.show()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UnrestrictedBackgroundPermissionDialog(onDismiss: () -> Unit) {
|
||||
var dontAskAgain by remember { mutableStateOf(false) }
|
||||
AlertDialog(onDismissRequest = onDismiss, title = { Text("Permission Required") },
|
||||
text = {
|
||||
Column {
|
||||
Text(stringResource(R.string.unrestricted_background_permission_text))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = dontAskAgain, onCheckedChange = { dontAskAgain = it })
|
||||
Text(stringResource(R.string.dont_ask_again))
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (dontAskAgain) prefs.edit().putBoolean("dont_ask_again_unrestricted_background", true).apply()
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
||||
this@MainActivity.startActivity(intent)
|
||||
onDismiss()
|
||||
}) { Text("OK") }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } }
|
||||
)
|
||||
}
|
||||
|
||||
fun checkAndRequestUnrestrictedBackgroundActivity(context: Context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
|
||||
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||
val isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(context.packageName)
|
||||
val dontAskAgain = prefs.getBoolean("dont_ask_again_unrestricted_background", false)
|
||||
if (!isIgnoringBatteryOptimizations && !dontAskAgain) {
|
||||
val composeView = ComposeView(this).apply {
|
||||
setContent { UnrestrictedBackgroundPermissionDialog(onDismiss = { (parent as? ViewGroup)?.removeView(this) }) }
|
||||
}
|
||||
(window.decorView as? ViewGroup)?.addView(composeView)
|
||||
}
|
||||
}
|
||||
|
||||
private var prevState: Int = 0
|
||||
private val bottomSheetCallback: BottomSheetCallback = object : BottomSheetCallback() {
|
||||
override fun onStateChanged(view: View, state: Int) {
|
||||
|
@ -205,7 +158,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
|
||||
private val isDrawerOpen: Boolean
|
||||
get() = drawerLayout?.isDrawerOpen(navDrawer)?:false
|
||||
get() = drawerLayout?.isDrawerOpen(navDrawer) == true
|
||||
|
||||
private val screenWidth: Int
|
||||
get() {
|
||||
|
@ -227,19 +180,13 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
|
||||
val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
// init shared preferences
|
||||
ioScope.launch {
|
||||
// RealmDB.apply { }
|
||||
NavDrawerFragment.getSharedPrefs(this@MainActivity)
|
||||
SwipeActions.getSharedPrefs(this@MainActivity)
|
||||
// QueuesFragment.getSharedPrefs(this@MainActivity)
|
||||
buildTags()
|
||||
monitorFeeds()
|
||||
// AudioPlayerFragment.getSharedPrefs(this@MainActivity)
|
||||
PlayerWidget.getSharedPrefs(this@MainActivity)
|
||||
// StatisticsFragment.getSharedPrefs(this@MainActivity)
|
||||
// OnlineFeedFragment.getSharedPrefs(this@MainActivity)
|
||||
// ItunesTopListLoader.getSharedPrefs(this@MainActivity)
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(Extras.generated_view_id.name, 0))
|
||||
|
@ -247,7 +194,6 @@ class MainActivity : CastEnabledActivity() {
|
|||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
super.onCreate(savedInstanceState)
|
||||
_binding = MainActivityBinding.inflate(layoutInflater)
|
||||
// setContentView(R.layout.main_activity)
|
||||
setContentView(binding.root)
|
||||
recycledViewPool.setMaxRecycledViews(R.id.view_type_episode_item, 25)
|
||||
dummyView = object : View(this) {}
|
||||
|
@ -321,6 +267,45 @@ class MainActivity : CastEnabledActivity() {
|
|||
observeDownloads()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UnrestrictedBackgroundPermissionDialog(onDismiss: () -> Unit) {
|
||||
var dontAskAgain by remember { mutableStateOf(false) }
|
||||
AlertDialog(onDismissRequest = onDismiss, title = { Text("Permission Required") },
|
||||
text = {
|
||||
Column {
|
||||
Text(stringResource(R.string.unrestricted_background_permission_text))
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Checkbox(checked = dontAskAgain, onCheckedChange = { dontAskAgain = it })
|
||||
Text(stringResource(R.string.dont_ask_again))
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
if (dontAskAgain) prefs.edit().putBoolean("dont_ask_again_unrestricted_background", true).apply()
|
||||
val intent = Intent()
|
||||
intent.action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
||||
this@MainActivity.startActivity(intent)
|
||||
onDismiss()
|
||||
}) { Text("OK") }
|
||||
},
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text("Cancel") } }
|
||||
)
|
||||
}
|
||||
|
||||
fun checkAndRequestUnrestrictedBackgroundActivity(context: Context) {
|
||||
// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return
|
||||
val powerManager = context.getSystemService(POWER_SERVICE) as PowerManager
|
||||
val isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(context.packageName)
|
||||
val dontAskAgain = prefs.getBoolean("dont_ask_again_unrestricted_background", false)
|
||||
if (!isIgnoringBatteryOptimizations && !dontAskAgain) {
|
||||
val composeView = ComposeView(this).apply {
|
||||
setContent { UnrestrictedBackgroundPermissionDialog(onDismiss = { (parent as? ViewGroup)?.removeView(this) }) }
|
||||
}
|
||||
(window.decorView as? ViewGroup)?.addView(composeView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun observeDownloads() {
|
||||
lifecycleScope.launch {
|
||||
withContext(Dispatchers.IO) { WorkManager.getInstance(this@MainActivity).pruneWork().result.get() }
|
||||
|
@ -718,7 +703,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
val s: Snackbar
|
||||
if (bottomSheet.state == BottomSheetBehavior.STATE_COLLAPSED) {
|
||||
s = Snackbar.make(mainView, text, duration)
|
||||
if (audioPlayerView.visibility == View.VISIBLE) s.setAnchorView(audioPlayerView)
|
||||
if (audioPlayerView.visibility == View.VISIBLE) s.anchorView = audioPlayerView
|
||||
} else s = Snackbar.make(binding.root, text, duration)
|
||||
|
||||
s.show()
|
||||
|
@ -813,7 +798,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
companion object {
|
||||
private val TAG: String = MainActivity::class.simpleName ?: "Anonymous"
|
||||
const val MAIN_FRAGMENT_TAG: String = "main"
|
||||
const val PREF_NAME: String = "MainActivityPrefs"
|
||||
// const val PREF_NAME: String = "MainActivityPrefs"
|
||||
|
||||
const val REQUEST_CODE_FIRST_PERMISSION = 1001
|
||||
const val REQUEST_CODE_SECOND_PERMISSION = 1002
|
||||
|
|
|
@ -90,11 +90,11 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
}
|
||||
if (listAdapter != null) {
|
||||
if (checkedCount == listAdapter!!.count) {
|
||||
selectAll.setVisible(false)
|
||||
deselectAll.setVisible(true)
|
||||
selectAll.isVisible = false
|
||||
deselectAll.isVisible = true
|
||||
} else {
|
||||
deselectAll.setVisible(false)
|
||||
selectAll.setVisible(true)
|
||||
deselectAll.isVisible = false
|
||||
selectAll.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
inflater.inflate(R.menu.opml_selection_options, menu)
|
||||
selectAll = menu.findItem(R.id.select_all_item)
|
||||
deselectAll = menu.findItem(R.id.deselect_all_item)
|
||||
deselectAll.setVisible(false)
|
||||
deselectAll.isVisible = false
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -168,15 +168,15 @@ class OpmlImportActivity : AppCompatActivity() {
|
|||
val itemId = item.itemId
|
||||
when (itemId) {
|
||||
R.id.select_all_item -> {
|
||||
selectAll.setVisible(false)
|
||||
selectAll.isVisible = false
|
||||
selectAllItems(true)
|
||||
deselectAll.setVisible(true)
|
||||
deselectAll.isVisible = true
|
||||
return true
|
||||
}
|
||||
R.id.deselect_all_item -> {
|
||||
deselectAll.setVisible(false)
|
||||
deselectAll.isVisible = false
|
||||
selectAllItems(false)
|
||||
selectAll.setVisible(true)
|
||||
selectAll.isVisible = true
|
||||
return true
|
||||
}
|
||||
android.R.id.home -> finish()
|
||||
|
|
|
@ -46,15 +46,11 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
_binding = SettingsActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
if (supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) == null) {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(binding.settingsContainer.id, MainPreferencesFragment(), FRAGMENT_TAG)
|
||||
.commit()
|
||||
}
|
||||
if (supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) == null)
|
||||
supportFragmentManager.beginTransaction().replace(binding.settingsContainer.id, MainPreferencesFragment(), FRAGMENT_TAG).commit()
|
||||
|
||||
val intent = intent
|
||||
if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) {
|
||||
openScreen(R.xml.preferences_autodownload)
|
||||
}
|
||||
if (intent.getBooleanExtra(OPEN_AUTO_DOWNLOAD_SETTINGS, false)) openScreen(R.xml.preferences_autodownload)
|
||||
}
|
||||
|
||||
private fun getPreferenceScreen(screen: Int): PreferenceFragmentCompat? {
|
||||
|
@ -81,27 +77,20 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
|
||||
intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(binding.settingsContainer.id, fragment!!)
|
||||
.addToBackStack(getString(getTitleOfPage(screen)))
|
||||
.commit()
|
||||
}
|
||||
} else
|
||||
supportFragmentManager.beginTransaction().replace(binding.settingsContainer.id, fragment!!).addToBackStack(getString(getTitleOfPage(screen))).commit()
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
if (supportFragmentManager.backStackEntryCount == 0) {
|
||||
finish()
|
||||
} else {
|
||||
if (supportFragmentManager.backStackEntryCount == 0) finish()
|
||||
else {
|
||||
val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
var view = currentFocus
|
||||
//If no view currently has focus, create a new one, just so we can grab a window token from it
|
||||
if (view == null) {
|
||||
view = View(this)
|
||||
}
|
||||
if (view == null) view = View(this)
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
supportFragmentManager.popBackStack()
|
||||
}
|
||||
|
@ -156,9 +145,7 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
fun onEventMainThread(event: FlowEvent.MessageEvent) {
|
||||
// Logd(FRAGMENT_TAG, "onEvent($event)")
|
||||
val s = Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG)
|
||||
if (event.action != null) {
|
||||
s.setAction(event.actionText) { event.action.accept(this) }
|
||||
}
|
||||
if (event.action != null) s.setAction(event.actionText) { event.action.accept(this) }
|
||||
s.show()
|
||||
}
|
||||
|
||||
|
@ -167,35 +154,16 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
|
|||
const val OPEN_AUTO_DOWNLOAD_SETTINGS: String = "OpenAutoDownloadSettings"
|
||||
@JvmStatic
|
||||
fun getTitleOfPage(preferences: Int): Int {
|
||||
when (preferences) {
|
||||
R.xml.preferences_downloads -> {
|
||||
return R.string.downloads_pref
|
||||
}
|
||||
R.xml.preferences_autodownload -> {
|
||||
return R.string.pref_automatic_download_title
|
||||
}
|
||||
R.xml.preferences_playback -> {
|
||||
return R.string.playback_pref
|
||||
}
|
||||
R.xml.preferences_import_export -> {
|
||||
return R.string.import_export_pref
|
||||
}
|
||||
R.xml.preferences_user_interface -> {
|
||||
return R.string.user_interface_label
|
||||
}
|
||||
R.xml.preferences_synchronization -> {
|
||||
return R.string.synchronization_pref
|
||||
}
|
||||
R.xml.preferences_notifications -> {
|
||||
return R.string.notification_pref_fragment
|
||||
}
|
||||
// R.xml.feed_settings -> {
|
||||
// return R.string.feed_settings_label
|
||||
// }
|
||||
R.xml.preferences_swipe -> {
|
||||
return R.string.swipeactions_label
|
||||
}
|
||||
else -> return R.string.settings_label
|
||||
return when (preferences) {
|
||||
R.xml.preferences_downloads -> R.string.downloads_pref
|
||||
R.xml.preferences_autodownload -> R.string.pref_automatic_download_title
|
||||
R.xml.preferences_playback -> R.string.playback_pref
|
||||
R.xml.preferences_import_export -> R.string.import_export_pref
|
||||
R.xml.preferences_user_interface -> R.string.user_interface_label
|
||||
R.xml.preferences_synchronization -> R.string.synchronization_pref
|
||||
R.xml.preferences_notifications -> R.string.notification_pref_fragment
|
||||
R.xml.preferences_swipe -> R.string.swipeactions_label
|
||||
else -> R.string.settings_label
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
|||
.setIcon(icon)
|
||||
.build()
|
||||
|
||||
setResult(Activity.RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
|
||||
setResult(RESULT_OK, ShortcutManagerCompat.createShortcutResultIntent(this, shortcut))
|
||||
finish()
|
||||
}
|
||||
|
||||
|
@ -127,11 +127,8 @@ class SelectSubscriptionActivity : AppCompatActivity() {
|
|||
val adapter: ArrayAdapter<String> = ArrayAdapter<String>(this@SelectSubscriptionActivity, R.layout.simple_list_item_multiple_choice_on_start, titles)
|
||||
binding.list.adapter = adapter
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
}
|
||||
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
@ -35,7 +35,6 @@ import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
|||
import ac.mdiq.podcini.ui.compose.ChaptersDialog
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.dialog.*
|
||||
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
|
||||
|
@ -48,10 +47,10 @@ import android.app.Dialog
|
|||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
|
@ -84,12 +83,7 @@ import kotlinx.coroutines.withContext
|
|||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/**
|
||||
* Activity for playing video files.
|
||||
*/
|
||||
|
||||
class VideoplayerActivity : CastEnabledActivity() {
|
||||
|
||||
private var _binding: VideoplayerActivityBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private lateinit var videoEpisodeFragment: VideoEpisodeFragment
|
||||
|
@ -185,10 +179,10 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
}
|
||||
|
||||
public override fun onUserLeaveHint() {
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) compatEnterPictureInPicture()
|
||||
super.onUserLeaveHint()
|
||||
if (!isInPictureInPictureMode()) compatEnterPictureInPicture()
|
||||
}
|
||||
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
procFlowEvents()
|
||||
|
@ -249,15 +243,15 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
val media = curMedia
|
||||
val isEpisodeMedia = (media is EpisodeMedia)
|
||||
|
||||
menu.findItem(R.id.show_home_reader_view).setVisible(false)
|
||||
menu.findItem(R.id.open_feed_item).setVisible(isEpisodeMedia) // EpisodeMedia implies it belongs to a Feed
|
||||
menu.findItem(R.id.show_home_reader_view).isVisible = false
|
||||
menu.findItem(R.id.open_feed_item).isVisible = isEpisodeMedia // EpisodeMedia implies it belongs to a Feed
|
||||
|
||||
val hasWebsiteLink = getWebsiteLinkWithFallback(media) != null
|
||||
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink)
|
||||
menu.findItem(R.id.visit_website_item).isVisible = hasWebsiteLink
|
||||
|
||||
val isItemAndHasLink = isEpisodeMedia && hasLinkToShare((media as EpisodeMedia).episodeOrFetch())
|
||||
val isItemAndHasLink = isEpisodeMedia && hasLinkToShare(media.episodeOrFetch())
|
||||
val isItemHasDownloadLink = isEpisodeMedia && (media as EpisodeMedia?)?.downloadUrl != null
|
||||
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
|
||||
menu.findItem(R.id.share_item).isVisible = hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink
|
||||
|
||||
// menu.findItem(R.id.add_to_favorites_item).setVisible(false)
|
||||
// menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
|
||||
|
@ -266,13 +260,13 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
// menu.findItem(R.id.remove_from_favorites_item).setVisible(videoEpisodeFragment.isFavorite)
|
||||
// }
|
||||
|
||||
menu.findItem(R.id.set_sleeptimer_item).setVisible(!isSleepTimerActive())
|
||||
menu.findItem(R.id.disable_sleeptimer_item).setVisible(isSleepTimerActive())
|
||||
menu.findItem(R.id.player_switch_to_audio_only).setVisible(true)
|
||||
menu.findItem(R.id.set_sleeptimer_item).isVisible = !isSleepTimerActive()
|
||||
menu.findItem(R.id.disable_sleeptimer_item).isVisible = isSleepTimerActive()
|
||||
menu.findItem(R.id.player_switch_to_audio_only).isVisible = true
|
||||
|
||||
menu.findItem(R.id.audio_controls).setVisible(audioTracks.size >= 2)
|
||||
menu.findItem(R.id.playback_speed).setVisible(true)
|
||||
menu.findItem(R.id.player_show_chapters).setVisible(true)
|
||||
menu.findItem(R.id.audio_controls).isVisible = audioTracks.size >= 2
|
||||
menu.findItem(R.id.playback_speed).isVisible = true
|
||||
menu.findItem(R.id.player_show_chapters).isVisible = true
|
||||
|
||||
if (videoMode == VideoMode.WINDOW_VIEW) {
|
||||
// menu.findItem(R.id.add_to_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
|
||||
|
@ -360,7 +354,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
}
|
||||
|
||||
private fun compatEnterPictureInPicture() {
|
||||
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
|
||||
if (videoMode == VideoMode.FULL_SCREEN_VIEW) supportActionBar?.hide()
|
||||
videoEpisodeFragment.hideVideoControls(false)
|
||||
enterPictureInPictureMode()
|
||||
|
@ -418,6 +412,16 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
return super.onKeyUp(keyCode, event)
|
||||
}
|
||||
|
||||
// fun supportsPictureInPicture(): Boolean {
|
||||
//// val packageManager = activity.packageManager
|
||||
// return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
||||
// }
|
||||
|
||||
override fun isInPictureInPictureMode(): Boolean {
|
||||
return if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) super.isInPictureInPictureMode
|
||||
else false
|
||||
}
|
||||
|
||||
class PlaybackControlsDialog : DialogFragment() {
|
||||
private lateinit var dialog: AlertDialog
|
||||
private var _binding: AudioControlsBinding? = null
|
||||
|
@ -474,7 +478,6 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class VideoEpisodeFragment : Fragment(), OnSeekBarChangeListener {
|
||||
private var _binding: VideoEpisodeFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -498,7 +501,7 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
private val onVideoviewTouched = View.OnTouchListener { v: View, event: MotionEvent ->
|
||||
Logd(TAG, "onVideoviewTouched ${event.action}")
|
||||
if (event.action != MotionEvent.ACTION_DOWN) return@OnTouchListener false
|
||||
if (PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) return@OnTouchListener true
|
||||
if (requireActivity().isInPictureInPictureMode()) return@OnTouchListener true
|
||||
videoControlsHider.removeCallbacks(hideVideoControls)
|
||||
Logd(TAG, "onVideoviewTouched $videoControlsVisible ${System.currentTimeMillis() - lastScreenTap}")
|
||||
if (System.currentTimeMillis() - lastScreenTap < 300) {
|
||||
|
@ -546,7 +549,6 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
videoControlsVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
|
@ -559,7 +561,6 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
return root
|
||||
}
|
||||
|
||||
|
||||
private fun newStatusHandler(): ServiceStatusHandler {
|
||||
return object : ServiceStatusHandler(requireActivity()) {
|
||||
override fun updatePlayButton(showPlay: Boolean) {
|
||||
|
@ -583,7 +584,6 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
@ -591,11 +591,10 @@ class VideoplayerActivity : CastEnabledActivity() {
|
|||
procFlowEvents()
|
||||
}
|
||||
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
cancelFlowEvents()
|
||||
if (!PictureInPictureUtil.isInPictureInPictureMode(requireActivity())) videoControlsHider.removeCallbacks(hideVideoControls)
|
||||
if (!requireActivity().isInPictureInPictureMode()) videoControlsHider.removeCallbacks(hideVideoControls)
|
||||
// Controller released; we will not receive buffering updates
|
||||
binding.progressBar.visibility = View.GONE
|
||||
}
|
||||
|
|
|
@ -1,16 +1,5 @@
|
|||
package ac.mdiq.podcini.ui.activity
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ActivityWidgetConfigBinding
|
||||
import ac.mdiq.podcini.databinding.PlayerWidgetBinding
|
||||
|
@ -19,6 +8,17 @@ import ac.mdiq.podcini.receiver.PlayerWidget
|
|||
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class WidgetConfigActivity : AppCompatActivity() {
|
||||
|
||||
|
@ -96,18 +96,15 @@ class WidgetConfigActivity : AppCompatActivity() {
|
|||
|
||||
private fun setInitialState() {
|
||||
PlayerWidget.getSharedPrefs(this)
|
||||
|
||||
// val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
|
||||
ckPlaybackSpeed.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_playback_speed.name + appWidgetId, true)
|
||||
ckRewind.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_rewind.name + appWidgetId, true)
|
||||
ckFastForward.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_fast_forward.name + appWidgetId, true)
|
||||
ckSkip.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_skip.name + appWidgetId, true)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val color = prefs!!.getInt(PlayerWidget.Prefs.widget_color.name + appWidgetId, PlayerWidget.DEFAULT_COLOR)
|
||||
val opacity = Color.alpha(color) * 100 / 0xFF
|
||||
|
||||
opacitySeekBar.setProgress(opacity, false)
|
||||
}
|
||||
val color = prefs!!.getInt(PlayerWidget.Prefs.widget_color.name + appWidgetId, PlayerWidget.DEFAULT_COLOR)
|
||||
val opacity = Color.alpha(color) * 100 / 0xFF
|
||||
opacitySeekBar.setProgress(opacity, false)
|
||||
displayPreviewPanel()
|
||||
}
|
||||
|
||||
|
@ -142,6 +139,6 @@ class WidgetConfigActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun getColorWithAlpha(color: Int, opacity: Int): Int {
|
||||
return Math.round(0xFF * (0.01 * opacity)).toInt() * 0x1000000 + (color and 0xffffff)
|
||||
return (0xFF * (0.01 * opacity)).roundToInt() * 0x1000000 + (color and 0xffffff)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
|
@ -58,7 +57,7 @@ fun ChaptersDialog(media: Playable, onDismissRequest: () -> Unit) {
|
|||
Text(ch.title ?: "No title", color = textColor, fontWeight = FontWeight.Bold)
|
||||
// Text(ch.link?: "")
|
||||
val duration = if (index + 1 < chapters.size) chapters[index + 1].start - ch.start
|
||||
else (media.getDuration() ?: 0) - ch.start
|
||||
else media.getDuration() - ch.start
|
||||
Text(stringResource(R.string.chapter_duration0) + getDurationStringLocalized(LocalContext.current, duration), color = textColor)
|
||||
}
|
||||
val playRes = if (index == currentChapterIndex) R.drawable.ic_replay else R.drawable.ic_play_48dp
|
||||
|
|
|
@ -6,8 +6,6 @@ import androidx.compose.foundation.border
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
@ -91,9 +89,8 @@ fun Spinner(items: List<String>, selectedItem: String, modifier: Modifier = Modi
|
|||
@Composable
|
||||
fun Spinner(items: List<String>, selectedIndex: Int, modifier: Modifier = Modifier, onItemSelected: (Int) -> Unit) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var currentSelectedIndex by remember { mutableStateOf(selectedIndex) }
|
||||
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
|
||||
BasicTextField(readOnly = true, value = items.getOrNull(currentSelectedIndex) ?: "Select Item", onValueChange = { },
|
||||
BasicTextField(readOnly = true, value = items.getOrNull(selectedIndex) ?: "Select Item", onValueChange = { },
|
||||
textStyle = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.onSurface, fontSize = MaterialTheme.typography.bodyLarge.fontSize, fontWeight = FontWeight.Bold),
|
||||
modifier = modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable, true), // Material3 requirement
|
||||
decorationBox = { innerTextField ->
|
||||
|
@ -106,7 +103,6 @@ fun Spinner(items: List<String>, selectedIndex: Int, modifier: Modifier = Modifi
|
|||
for (i in items.indices) {
|
||||
DropdownMenuItem(text = { Text(items[i]) },
|
||||
onClick = {
|
||||
currentSelectedIndex = i
|
||||
onItemSelected(i)
|
||||
expanded = false
|
||||
}
|
||||
|
@ -132,36 +128,6 @@ fun CustomToast(message: String, durationMillis: Long = 2000L, onDismiss: () ->
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AutoCompleteTextView(suggestions: List<String>, onItemSelected: (String) -> Unit, modifier: Modifier = Modifier) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
|
||||
Column(modifier = modifier) {
|
||||
TextField(value = text,
|
||||
onValueChange = { text = it },
|
||||
label = { Text("Search") },
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { expanded = !expanded }) {
|
||||
Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = "Expand")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (expanded) {
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
suggestions.forEach { suggestion ->
|
||||
DropdownMenuItem(text = {Text(suggestion)}, onClick = {
|
||||
onItemSelected(suggestion)
|
||||
text = suggestion
|
||||
expanded = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun LargeTextEditingDialog(textState: TextFieldValue, onTextChange: (TextFieldValue) -> Unit, onDismissRequest: () -> Unit, onSave: (String) -> Unit) {
|
||||
Dialog(onDismissRequest = { onDismissRequest() }, properties = DialogProperties(usePlatformDefaultWidth = false)) {
|
||||
|
|
|
@ -112,24 +112,16 @@ fun InforBar(text: MutableState<String>, leftAction: MutableState<SwipeAction>,
|
|||
Logd("InforBar", "textState: ${text.value}")
|
||||
Row {
|
||||
Icon(imageVector = ImageVector.vectorResource(leftAction.value.getActionIcon()), tint = textColor, contentDescription = "left_action_icon",
|
||||
modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp)
|
||||
.clickable(onClick = actionConfig))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "left_arrow", modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp))
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "left_arrow",
|
||||
modifier = Modifier.width(24.dp).height(24.dp))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(text.value, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow",
|
||||
modifier = Modifier.width(24.dp).height(24.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(rightAction.value.getActionIcon()), tint = textColor, contentDescription = "right_action_icon",
|
||||
modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp)
|
||||
.clickable(onClick = actionConfig))
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -213,20 +205,6 @@ class EpisodeVM(var episode: Episode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override fun equals(other: Any?): Boolean {
|
||||
// if (this === other) return true
|
||||
// if (javaClass != other?.javaClass) return false
|
||||
// other as EpisodeVM
|
||||
//
|
||||
// if (episode.id != other.episode.id) return false
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// override fun hashCode(): Int {
|
||||
// var result = episode.id.hashCode()
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -333,9 +311,7 @@ fun PutToQueueDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
if (removeChecked) {
|
||||
val toRemove = mutableSetOf<Long>()
|
||||
val toRemoveCur = mutableListOf<Episode>()
|
||||
selected.forEach { e ->
|
||||
if (curQueue.contains(e)) toRemoveCur.add(e)
|
||||
}
|
||||
selected.forEach { e -> if (curQueue.contains(e)) toRemoveCur.add(e) }
|
||||
selected.forEach { e ->
|
||||
for (q in queues) {
|
||||
if (q.contains(e)) {
|
||||
|
@ -347,13 +323,9 @@ fun PutToQueueDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) }
|
||||
if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
|
||||
}
|
||||
selected.forEach { e ->
|
||||
runBlocking { addToQueueSync(e, toQueue) }
|
||||
}
|
||||
selected.forEach { e -> runBlocking { addToQueueSync(e, toQueue) } }
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text("Confirm")
|
||||
}
|
||||
}) { Text("Confirm") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -395,11 +367,7 @@ fun ShelveDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
e_.media?.id = e_.id
|
||||
} else {
|
||||
val feed = realm.query(Feed::class).query("id == $0", e_.feedId).first().find()
|
||||
if (feed != null) {
|
||||
upsertBlk(feed) {
|
||||
it.episodes.remove(e_)
|
||||
}
|
||||
}
|
||||
if (feed != null) upsertBlk(feed) { it.episodes.remove(e_) }
|
||||
}
|
||||
upsertBlk(e_) {
|
||||
it.feed = toFeed
|
||||
|
@ -407,13 +375,9 @@ fun ShelveDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
eList.add(it)
|
||||
}
|
||||
}
|
||||
upsertBlk(toFeed!!) {
|
||||
it.episodes.addAll(eList)
|
||||
}
|
||||
upsertBlk(toFeed!!) { it.episodes.addAll(eList) }
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text("Confirm")
|
||||
}
|
||||
}) { Text("Confirm") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -464,9 +428,7 @@ fun EraseEpisodesDialog(selected: List<Episode>, feed: Feed?, onDismissRequest:
|
|||
} catch (e: Throwable) { Log.e("EraseEpisodesDialog", Log.getStackTraceString(e)) }
|
||||
}
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text("Confirm")
|
||||
}
|
||||
}) { Text("Confirm") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -490,9 +452,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
|
|||
|
||||
val showConfirmYoutubeDialog = remember { mutableStateOf(false) }
|
||||
val youtubeUrls = remember { mutableListOf<String>() }
|
||||
ConfirmAddYoutubeEpisode(youtubeUrls, showConfirmYoutubeDialog.value, onDismissRequest = {
|
||||
showConfirmYoutubeDialog.value = false
|
||||
})
|
||||
ConfirmAddYoutubeEpisode(youtubeUrls, showConfirmYoutubeDialog.value, onDismissRequest = { showConfirmYoutubeDialog.value = false })
|
||||
|
||||
var showChooseRatingDialog by remember { mutableStateOf(false) }
|
||||
if (showChooseRatingDialog) ChooseRatingDialog(selected) { showChooseRatingDialog = false }
|
||||
|
@ -513,152 +473,133 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
|
|||
fun EpisodeSpeedDial(modifier: Modifier = Modifier) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
val options = mutableListOf<@Composable () -> Unit>(
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_delete: ${selected.size}")
|
||||
runOnIOScope {
|
||||
for (item_ in selected) {
|
||||
var item = item_
|
||||
if (!item.isDownloaded && item.feed?.isLocalFeed != true) continue
|
||||
val media = item.media
|
||||
if (media != null) {
|
||||
val almostEnded = hasAlmostEnded(media)
|
||||
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
|
||||
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
|
||||
deleteEpisodeMedia(activity, item)
|
||||
}
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_delete: ${selected.size}")
|
||||
runOnIOScope {
|
||||
for (item_ in selected) {
|
||||
var item = item_
|
||||
if (!item.isDownloaded && item.feed?.isLocalFeed != true) continue
|
||||
val media = item.media
|
||||
if (media != null) {
|
||||
val almostEnded = hasAlmostEnded(media)
|
||||
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
|
||||
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
|
||||
deleteEpisodeMedia(activity, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
// LocalDeleteModal.deleteEpisodesWarnLocal(activity, selected)
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media")
|
||||
Text(stringResource(id = R.string.delete_episode_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_download: ${selected.size}")
|
||||
for (episode in selected) {
|
||||
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode)
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_download: ${selected.size}")
|
||||
for (episode in selected) {
|
||||
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode)
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download")
|
||||
Text(stringResource(id = R.string.download_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
showPlayStateDialog = true
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_mark_played: ${selected.size}")
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
showPlayStateDialog = true
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_mark_played: ${selected.size}")
|
||||
// setPlayState(PlayState.UNSPECIFIED.code, false, *selected.toTypedArray())
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_mark_played), "Toggle played state")
|
||||
Text(stringResource(id = R.string.set_play_state_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_remove: ${selected.size}")
|
||||
runOnIOScope {
|
||||
for (item_ in selected) {
|
||||
var item = item_
|
||||
val media = item.media
|
||||
if (media != null) {
|
||||
val almostEnded = hasAlmostEnded(media)
|
||||
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
|
||||
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
|
||||
}
|
||||
if (item.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, item)
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_remove: ${selected.size}")
|
||||
runOnIOScope {
|
||||
for (item_ in selected) {
|
||||
var item = item_
|
||||
val media = item.media
|
||||
if (media != null) {
|
||||
val almostEnded = hasAlmostEnded(media)
|
||||
if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false)
|
||||
if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() }
|
||||
}
|
||||
removeFromQueueSync(curQueue, *selected.toTypedArray())
|
||||
// removeFromQueue(*selected.toTypedArray())
|
||||
if (item.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, item)
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
removeFromQueueSync(curQueue, *selected.toTypedArray())
|
||||
// removeFromQueue(*selected.toTypedArray())
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue")
|
||||
Text(stringResource(id = R.string.remove_from_queue_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_play: ${selected.size}")
|
||||
Queues.addToQueue(*selected.toTypedArray())
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_play: ${selected.size}")
|
||||
Queues.addToQueue(*selected.toTypedArray())
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "Add to active queue")
|
||||
Text(stringResource(id = R.string.add_to_queue_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "shelve_label: ${selected.size}")
|
||||
showShelveDialog = true
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "shelve_label: ${selected.size}")
|
||||
showShelveDialog = true
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_shelves_24), "Shelve")
|
||||
Text(stringResource(id = R.string.shelve_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_play: ${selected.size}")
|
||||
showPutToQueueDialog = true
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_playlist_play: ${selected.size}")
|
||||
showPutToQueueDialog = true
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "Add to queue...")
|
||||
Text(stringResource(id = R.string.put_in_queue_label)) } },
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_star: ${selected.size}")
|
||||
showChooseRatingDialog = true
|
||||
isExpanded = false
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_star: ${selected.size}")
|
||||
showChooseRatingDialog = true
|
||||
isExpanded = false
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_star), "Set rating")
|
||||
Text(stringResource(id = R.string.set_rating_label)) } },
|
||||
)
|
||||
if (selected.isNotEmpty() && selected[0].isRemote.value)
|
||||
options.add {
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "reserve: ${selected.size}")
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
youtubeUrls.clear()
|
||||
for (e in selected) {
|
||||
Logd(TAG, "downloadUrl: ${e.media?.downloadUrl}")
|
||||
val url = URL(e.media?.downloadUrl ?: "")
|
||||
if ((isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url)) {
|
||||
youtubeUrls.add(e.media!!.downloadUrl!!)
|
||||
} else addToMiscSyndicate(e)
|
||||
}
|
||||
Logd(TAG, "youtubeUrls: ${youtubeUrls.size}")
|
||||
withContext(Dispatchers.Main) {
|
||||
showConfirmYoutubeDialog.value = youtubeUrls.isNotEmpty()
|
||||
}
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "reserve: ${selected.size}")
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
youtubeUrls.clear()
|
||||
for (e in selected) {
|
||||
Logd(TAG, "downloadUrl: ${e.media?.downloadUrl}")
|
||||
val url = URL(e.media?.downloadUrl ?: "")
|
||||
if ((isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url)) {
|
||||
youtubeUrls.add(e.media!!.downloadUrl!!)
|
||||
} else addToMiscSyndicate(e)
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Logd(TAG, "youtubeUrls: ${youtubeUrls.size}")
|
||||
withContext(Dispatchers.Main) {
|
||||
showConfirmYoutubeDialog.value = youtubeUrls.isNotEmpty()
|
||||
}
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(Icons.Filled.AddCircle, "Reserve episodes")
|
||||
Text(stringResource(id = R.string.reserve_episodes_label))
|
||||
}
|
||||
}
|
||||
if (feed != null && feed.id <= MAX_SYNTHETIC_ID) {
|
||||
options.add {
|
||||
Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
showEraseDialog = true
|
||||
Logd(TAG, "reserve: ${selected.size}")
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp).clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
showEraseDialog = true
|
||||
Logd(TAG, "reserve: ${selected.size}")
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_delete_forever_24), "Erase episodes")
|
||||
Text(stringResource(id = R.string.erase_episodes_label))
|
||||
}
|
||||
|
@ -668,14 +609,9 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
|
|||
val scrollState = rememberScrollState()
|
||||
Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) {
|
||||
if (isExpanded) options.forEachIndexed { _, button ->
|
||||
FloatingActionButton(modifier = Modifier
|
||||
.padding(start = 4.dp, bottom = 6.dp)
|
||||
.height(40.dp),
|
||||
containerColor = Color.LightGray,
|
||||
onClick = {}) { button() }
|
||||
FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp), containerColor = Color.LightGray, onClick = {}) { button() }
|
||||
}
|
||||
FloatingActionButton(containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.secondary,
|
||||
FloatingActionButton(containerColor = MaterialTheme.colorScheme.secondaryContainer, contentColor = MaterialTheme.colorScheme.secondary,
|
||||
onClick = { isExpanded = !isExpanded }) { Icon(Icons.Filled.Edit, "Edit") }
|
||||
}
|
||||
}
|
||||
|
@ -807,8 +743,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList<EpisodeVM>, feed:
|
|||
if (isDownloading() && vm.dlPercent >= 0) CircularProgressIndicator(progress = { 0.01f * vm.dlPercent },
|
||||
strokeWidth = 4.dp, color = textColor, modifier = Modifier.width(30.dp).height(35.dp))
|
||||
}
|
||||
if (vm.showAltActionsDialog) actionButton.AltActionsDialog(activity, vm.showAltActionsDialog,
|
||||
onDismiss = { vm.showAltActionsDialog = false })
|
||||
if (vm.showAltActionsDialog) actionButton.AltActionsDialog(activity, vm.showAltActionsDialog, onDismiss = { vm.showAltActionsDialog = false })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1037,9 +972,7 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
|
|||
}
|
||||
onFilterChanged(filterValues)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(item.values[0].displayName), color = textColor)
|
||||
}
|
||||
) { Text(text = stringResource(item.values[0].displayName), color = textColor) }
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
OutlinedButton(
|
||||
modifier = Modifier.padding(0.dp), border = BorderStroke(2.dp, if (selectedIndex != 1) textColor else Color.Green),
|
||||
|
@ -1055,9 +988,7 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
|
|||
}
|
||||
onFilterChanged(filterValues)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(item.values[1].displayName), color = textColor)
|
||||
}
|
||||
) { Text(text = stringResource(item.values[1].displayName), color = textColor) }
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
}
|
||||
} else {
|
||||
|
@ -1133,9 +1064,7 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
|
|||
else filterValues.remove(item.values[index].filterId)
|
||||
onFilterChanged(filterValues)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(item.values[index].displayName), maxLines = 1, color = textColor)
|
||||
}
|
||||
) { Text(text = stringResource(item.values[index].displayName), maxLines = 1, color = textColor) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1145,15 +1074,9 @@ fun EpisodesFilterDialog(filter: EpisodeFilter? = null, filtersDisabled: Mutable
|
|||
Button(onClick = {
|
||||
selectNone = true
|
||||
onFilterChanged(setOf(""))
|
||||
}) {
|
||||
Text(stringResource(R.string.reset))
|
||||
}
|
||||
}) { Text(stringResource(R.string.reset)) }
|
||||
Spacer(Modifier.weight(0.4f))
|
||||
Button(onClick = {
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Text(stringResource(R.string.close_label))
|
||||
}
|
||||
Button(onClick = { onDismissRequest() }) { Text(stringResource(R.string.close_label)) }
|
||||
Spacer(Modifier.weight(0.3f))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -244,7 +244,7 @@ fun RenameOrCreateSyntheticFeed(feed_: Feed? = null, onDismissRequest: () -> Uni
|
|||
Row {
|
||||
Button({ onDismissRequest() }) { Text(stringResource(R.string.cancel_label)) }
|
||||
Button({
|
||||
val feed = if (feed_ == null) createSynthetic(0, name, hasVideo) else feed_
|
||||
val feed = feed_ ?: createSynthetic(0, name, hasVideo)
|
||||
if (feed_ == null) {
|
||||
feed.type = if (isYoutube) Feed.FeedType.YOUTUBE.name else Feed.FeedType.RSS.name
|
||||
if (hasVideo) feed.preferences!!.videoModePolicy = VideoMode.WINDOW_VIEW
|
||||
|
|
|
@ -8,7 +8,6 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
|
|||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.EpisodeFilter
|
||||
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
|
|
|
@ -97,7 +97,7 @@ import java.text.NumberFormat
|
|||
import kotlin.math.max
|
||||
|
||||
class AudioPlayerFragment : Fragment() {
|
||||
val prefs: SharedPreferences by lazy { requireContext().getSharedPreferences(PREF, Context.MODE_PRIVATE) }
|
||||
val prefs: SharedPreferences by lazy { requireContext().getSharedPreferences("AudioPlayerFragmentPrefs", Context.MODE_PRIVATE) }
|
||||
|
||||
private var isCollapsed by mutableStateOf(true)
|
||||
|
||||
|
@ -999,13 +999,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
val TAG = AudioPlayerFragment::class.simpleName ?: "Anonymous"
|
||||
var media3Controller: MediaController? = null
|
||||
|
||||
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)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,6 @@ import ac.mdiq.podcini.util.IntentUtils
|
|||
import ac.mdiq.podcini.util.Logd
|
||||
import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.speech.tts.TextToSpeech
|
||||
import android.text.TextUtils
|
||||
|
@ -90,9 +89,6 @@ import okhttp3.Request.Builder
|
|||
import java.io.File
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Displays information about an Episode (FeedItem) and actions.
|
||||
*/
|
||||
class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
private var _binding: ComposeFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -348,8 +344,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
if (episode == null) return false
|
||||
val notes = episode!!.description
|
||||
if (!notes.isNullOrEmpty()) {
|
||||
val shareText = if (Build.VERSION.SDK_INT >= 24) HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
else HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
// val shareText = if (Build.VERSION.SDK_INT >= 24) HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
// else HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
val shareText = HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
val context = requireContext()
|
||||
val intent = ShareCompat.IntentBuilder(context)
|
||||
.setType("text/plain")
|
||||
|
@ -774,8 +771,8 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
if (textSpeech?.isVisible == true) {
|
||||
if (ttsPlaying) textSpeech.setIcon(R.drawable.ic_pause) else textSpeech.setIcon(R.drawable.ic_play_24dp)
|
||||
}
|
||||
menu.findItem(R.id.share_notes)?.setVisible(readMode)
|
||||
menu.findItem(R.id.switchJS)?.setVisible(!readMode)
|
||||
menu.findItem(R.id.share_notes)?.isVisible = readMode
|
||||
menu.findItem(R.id.switchJS)?.isVisible = !readMode
|
||||
val btn = menu.findItem(R.id.switch_home)
|
||||
if (readMode) btn?.setIcon(R.drawable.baseline_home_24)
|
||||
else btn?.setIcon(R.drawable.outline_home_24)
|
||||
|
|
|
@ -62,9 +62,6 @@ import java.util.*
|
|||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Semaphore
|
||||
|
||||
/**
|
||||
* Displays a list of FeedItems.
|
||||
*/
|
||||
class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
|
||||
private var _binding: ComposeFragmentBinding? = null
|
||||
|
@ -384,10 +381,10 @@ import java.util.concurrent.Semaphore
|
|||
private fun updateToolbar() {
|
||||
if (feed == null) return
|
||||
|
||||
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
|
||||
binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
|
||||
if (StringUtils.isBlank(feed!!.link)) binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
|
||||
if (feed!!.isLocalFeed) binding.toolbar.menu.findItem(R.id.share_feed).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.visit_website_item).isVisible = feed!!.link != null
|
||||
binding.toolbar.menu.findItem(R.id.refresh_complete_item).isVisible = feed!!.isPaged
|
||||
if (StringUtils.isBlank(feed!!.link)) binding.toolbar.menu.findItem(R.id.visit_website_item).isVisible = false
|
||||
if (feed!!.isLocalFeed) binding.toolbar.menu.findItem(R.id.share_feed).isVisible = false
|
||||
}
|
||||
|
||||
// override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
|
@ -451,20 +448,21 @@ import java.util.concurrent.Semaphore
|
|||
return true
|
||||
}
|
||||
|
||||
// TODO: not really needed
|
||||
private fun onQueueEvent(event: FlowEvent.QueueEvent) {
|
||||
if (feed == null || episodes.isEmpty()) return
|
||||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item = event.episodes[i++]
|
||||
if (item.feedId != feed!!.id) continue
|
||||
val pos: Int = ieMap[item.id] ?: -1
|
||||
if (pos >= 0) {
|
||||
// episodes[pos].inQueueState.value = event.inQueue()
|
||||
// queueChanged++
|
||||
}
|
||||
break
|
||||
}
|
||||
// var i = 0
|
||||
// val size: Int = event.episodes.size
|
||||
// while (i < size) {
|
||||
// val item = event.episodes[i++]
|
||||
// if (item.feedId != feed!!.id) continue
|
||||
// val pos: Int = ieMap[item.id] ?: -1
|
||||
// if (pos >= 0) {
|
||||
//// episodes[pos].inQueueState.value = event.inQueue()
|
||||
//// queueChanged++
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
}
|
||||
|
||||
private fun onPlayEvent(event: FlowEvent.PlayEvent) {
|
||||
|
|
|
@ -318,11 +318,10 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
|
||||
private fun refreshToolbarState() {
|
||||
toolbar.menu?.findItem(R.id.reconnect_local_folder)?.setVisible(feed.isLocalFeed)
|
||||
toolbar.menu?.findItem(R.id.share_item)?.setVisible(!feed.isLocalFeed)
|
||||
toolbar.menu?.findItem(R.id.visit_website_item)
|
||||
?.setVisible(feed.link != null && IntentUtils.isCallable(requireContext(), Intent(Intent.ACTION_VIEW, Uri.parse(feed.link))))
|
||||
toolbar.menu?.findItem(R.id.edit_feed_url_item)?.setVisible(!feed.isLocalFeed)
|
||||
toolbar.menu?.findItem(R.id.reconnect_local_folder)?.isVisible = feed.isLocalFeed
|
||||
toolbar.menu?.findItem(R.id.share_item)?.isVisible = !feed.isLocalFeed
|
||||
toolbar.menu?.findItem(R.id.visit_website_item)?.isVisible = feed.link != null && IntentUtils.isCallable(requireContext(), Intent(Intent.ACTION_VIEW, Uri.parse(feed.link)))
|
||||
toolbar.menu?.findItem(R.id.edit_feed_url_item)?.isVisible = !feed.isLocalFeed
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
|
|
|
@ -104,7 +104,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge, color = textColor)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated ?: true) }
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated != false) }
|
||||
Switch(checked = checked, modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
|
@ -138,7 +138,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Text(text = stringResource(R.string.pref_stream_over_download_title), style = MaterialTheme.typography.titleLarge, color = textColor)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.prefStreamOverDownload ?: false) }
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.prefStreamOverDownload == true) }
|
||||
Switch(checked = checked, modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
|
@ -213,7 +213,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
Spacer(modifier = Modifier.width(20.dp))
|
||||
Text(text = stringResource(R.string.audo_add_new_queue), style = MaterialTheme.typography.titleLarge, color = textColor)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue ?: true) }
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue != false) }
|
||||
Switch(checked = checked, modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
|
@ -302,7 +302,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
}
|
||||
if (isEnableAutodownload && feed?.type != Feed.FeedType.YOUTUBE.name) {
|
||||
// auto download
|
||||
var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload ?: false) }
|
||||
var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload == true) }
|
||||
Column {
|
||||
Row(Modifier.fillMaxWidth()) {
|
||||
Text(text = stringResource(R.string.auto_download_label), style = MaterialTheme.typography.titleLarge, color = textColor)
|
||||
|
@ -341,7 +341,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
Row(Modifier.fillMaxWidth()) {
|
||||
Text(text = stringResource(R.string.pref_auto_download_counting_played_title), style = MaterialTheme.typography.titleLarge, color = textColor)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.countingPlayed ?: true) }
|
||||
var checked by remember { mutableStateOf(feed?.preferences?.countingPlayed != false) }
|
||||
Switch(checked = checked, modifier = Modifier.height(24.dp),
|
||||
onCheckedChange = {
|
||||
checked = it
|
||||
|
@ -800,9 +800,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
.setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int ->
|
||||
val newSpeed = if (binding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL
|
||||
else binding.seekBar.currentSpeed
|
||||
feed = upsertBlk(feed!!) {
|
||||
it.preferences?.playSpeed = newSpeed
|
||||
}
|
||||
feed = upsertBlk(feed!!) { it.preferences?.playSpeed = newSpeed }
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.create()
|
||||
|
|
|
@ -96,9 +96,9 @@ class HistoryFragment : BaseEpisodesFragment() {
|
|||
|
||||
override fun updateToolbar() {
|
||||
// Not calling super, as we do not have a refresh button that could be updated
|
||||
toolbar.menu.findItem(R.id.episodes_sort).setVisible(episodes.isNotEmpty())
|
||||
toolbar.menu.findItem(R.id.filter_items).setVisible(episodes.isNotEmpty())
|
||||
toolbar.menu.findItem(R.id.clear_history_item).setVisible(episodes.isNotEmpty())
|
||||
toolbar.menu.findItem(R.id.episodes_sort).isVisible = episodes.isNotEmpty()
|
||||
toolbar.menu.findItem(R.id.filter_items).isVisible = episodes.isNotEmpty()
|
||||
toolbar.menu.findItem(R.id.clear_history_item).isVisible = episodes.isNotEmpty()
|
||||
|
||||
swipeActions.setFilter(getFilter())
|
||||
var info = "${episodes.size} episodes"
|
||||
|
|
|
@ -5,11 +5,11 @@ import ac.mdiq.podcini.databinding.ComposeFragmentBinding
|
|||
import ac.mdiq.podcini.net.feed.FeedUpdateManager
|
||||
import ac.mdiq.podcini.storage.database.Feeds.getFeed
|
||||
import ac.mdiq.podcini.storage.database.Feeds.getFeedByTitleAndAuthor
|
||||
import ac.mdiq.podcini.storage.database.LogsAndStats.DownloadResultComparator
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.model.Rating.Companion.fromCode
|
||||
import ac.mdiq.podcini.storage.utils.DownloadResultComparator
|
||||
import ac.mdiq.podcini.ui.actions.DownloadActionButton
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.activity.ShareReceiverActivity.Companion.receiveShared
|
||||
|
@ -301,7 +301,7 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
menu.findItem(R.id.clear_logs_item).setVisible(shareLogs.isNotEmpty())
|
||||
menu.findItem(R.id.clear_logs_item).isVisible = shareLogs.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun clearAllLogs() {
|
||||
|
|
|
@ -59,6 +59,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
|
||||
val TAG = this::class.simpleName ?: "Anonymous"
|
||||
|
@ -68,13 +69,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
|
|||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
checkHiddenItems()
|
||||
getRecentPodcasts()
|
||||
val composeView = ComposeView(requireContext()).apply {
|
||||
setContent {
|
||||
CustomTheme(requireContext()) {
|
||||
MainView()
|
||||
}
|
||||
}
|
||||
}
|
||||
val composeView = ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { MainView() } } }
|
||||
|
||||
Logd(TAG, "fragment onCreateView")
|
||||
setupDrawerRoundBackground(composeView)
|
||||
|
@ -87,7 +82,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
|
|||
navigationBarHeight = if (requireActivity().window.navigationBarDividerColor == Color.TRANSPARENT) 0f
|
||||
else 1 * resources.displayMetrics.density // Assuming the divider is 1dp in height
|
||||
}
|
||||
val bottomInset = max(0.0, Math.round(bars.bottom - navigationBarHeight).toDouble()).toFloat()
|
||||
val bottomInset = max(0.0, (bars.bottom - navigationBarHeight).roundToInt().toDouble()).toFloat()
|
||||
(view.layoutParams as ViewGroup.MarginLayoutParams).bottomMargin = bottomInset.toInt()
|
||||
insets
|
||||
}
|
||||
|
|
|
@ -41,8 +41,11 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.annotation.UiThread
|
||||
import androidx.collection.ArrayMap
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
|
@ -68,7 +71,6 @@ import org.jsoup.nodes.Document
|
|||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.concurrent.Volatile
|
||||
|
||||
/**
|
||||
* Downloads a feed from a feed URL and parses it. Subclasses can display the
|
||||
|
@ -736,11 +738,11 @@ class OnlineFeedFragment : Fragment() {
|
|||
return PREF_NAME
|
||||
}
|
||||
override fun updateToolbar() {
|
||||
binding.toolbar.menu.findItem(R.id.episodes_sort).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.episodes_sort).isVisible = false
|
||||
// binding.toolbar.menu.findItem(R.id.refresh_item).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.action_search).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.action_search).isVisible = false
|
||||
// binding.toolbar.menu.findItem(R.id.action_favorites).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.filter_items).setVisible(false)
|
||||
binding.toolbar.menu.findItem(R.id.filter_items).isVisible = false
|
||||
infoBarText.value = "${episodes.size} episodes"
|
||||
}
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
|
|
|
@ -36,10 +36,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
/**
|
||||
* Provides actions for adding new podcast subscriptions.
|
||||
*/
|
||||
|
||||
class OnlineSearchFragment : Fragment() {
|
||||
|
||||
private var _binding: AddfeedBinding? = null
|
||||
|
@ -56,11 +52,11 @@ class OnlineSearchFragment : Fragment() {
|
|||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
super.onCreateView(inflater, container, savedInstanceState)
|
||||
_binding = AddfeedBinding.inflate(inflater)
|
||||
activity = getActivity() as? MainActivity
|
||||
// activity = activity
|
||||
Logd(TAG, "fragment onCreateView")
|
||||
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
|
||||
if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
|
||||
(getActivity() as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
|
||||
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
|
||||
|
||||
binding.searchButton.setOnClickListener { performSearch() }
|
||||
binding.searchVistaGuideButton.setOnClickListener { activity?.loadChildFragment(SearchResultsFragment.newInstance(VistaGuidePodcastSearcher::class.java)) }
|
||||
|
@ -172,12 +168,12 @@ class OnlineSearchFragment : Fragment() {
|
|||
withContext(Dispatchers.Main) {
|
||||
if (feed != null) {
|
||||
val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id)
|
||||
(getActivity() as MainActivity).loadChildFragment(fragment)
|
||||
(activity as MainActivity).loadChildFragment(fragment)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
Log.e(TAG, Log.getStackTraceString(e))
|
||||
(getActivity() as MainActivity).showSnackbarAbovePlayer(e.localizedMessage?: "No messaage", Snackbar.LENGTH_LONG)
|
||||
(activity as MainActivity).showSnackbarAbovePlayer(e.localizedMessage?: "No messaage", Snackbar.LENGTH_LONG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
queueNames = queues.map { it.name }.toTypedArray()
|
||||
spinnerTexts.clear()
|
||||
spinnerTexts.addAll(queues.map { "${it.name} : ${it.size()}" })
|
||||
curIndex = queues.indexOf(curQueue)
|
||||
// curIndex = queues.indexOf(curQueue)
|
||||
|
||||
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
|
||||
toolbar.inflateMenu(R.menu.queue)
|
||||
|
@ -156,7 +156,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Logd(TAG, "Queue selected: $queues[index].name")
|
||||
val prevQueueSize = curQueue.size()
|
||||
curQueue = upsertBlk(queues[index]) { it.update() }
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.setVisible(curQueue.name != "Default")
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.isVisible = curQueue.name != "Default"
|
||||
loadCurQueue(true)
|
||||
playbackService?.notifyCurQueueItemsChanged(max(prevQueueSize, curQueue.size()))
|
||||
}
|
||||
|
@ -246,7 +246,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
items(feedsAssociated.size, key = {index -> feedsAssociated[index].id}) { index ->
|
||||
val feed by remember { mutableStateOf(feedsAssociated[index]) }
|
||||
ConstraintLayout {
|
||||
val (coverImage, episodeCount, rating, error) = createRefs()
|
||||
val (coverImage, episodeCount, rating, _) = createRefs()
|
||||
val imgLoc = remember(feed) { feed.imageUrl }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
|
@ -374,7 +374,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
queues = realm.query(PlayQueue::class).find()
|
||||
queueNames = queues.map { it.name }.toTypedArray()
|
||||
curIndex = queues.indexOf(curQueue)
|
||||
curIndex = queues.indexOfFirst { it.id == curQueue.id }
|
||||
spinnerTexts.clear()
|
||||
spinnerTexts.addAll(queues.map { "${it.name} : ${it.size()}" })
|
||||
refreshMenuItems()
|
||||
|
@ -451,20 +451,20 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
|
||||
private fun refreshMenuItems() {
|
||||
if (showBin) {
|
||||
toolbar.menu?.findItem(R.id.queue_sort)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.associated_feed)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.add_queue)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.action_search)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.queue_sort)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.associated_feed)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.add_queue)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.action_search)?.isVisible = false
|
||||
} else {
|
||||
toolbar.menu?.findItem(R.id.action_search)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.queue_sort)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.associated_feed)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.setChecked(isQueueLocked)
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.setVisible(!isQueueKeepSorted)
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.setVisible(curQueue.name != "Default")
|
||||
toolbar.menu?.findItem(R.id.add_queue)?.setVisible(queueNames.size < 9)
|
||||
toolbar.menu?.findItem(R.id.action_search)?.isVisible = true
|
||||
toolbar.menu?.findItem(R.id.queue_sort)?.isVisible = true
|
||||
toolbar.menu?.findItem(R.id.associated_feed)?.isVisible = true
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.isChecked = isQueueLocked
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.isVisible = !isQueueKeepSorted
|
||||
toolbar.menu?.findItem(R.id.rename_queue)?.isVisible = curQueue.name != "Default"
|
||||
toolbar.menu?.findItem(R.id.add_queue)?.isVisible = queueNames.size < 9
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -584,7 +584,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
upsertBlk(newQueue) {}
|
||||
queues = realm.query(PlayQueue::class).find()
|
||||
queueNames = queues.map { it.name }.toTypedArray()
|
||||
curIndex = queues.indexOf(curQueue)
|
||||
curIndex = queues.indexOfFirst { it.id == curQueue.id }
|
||||
spinnerTexts.clear()
|
||||
spinnerTexts.addAll(queues.map { "${it.name} : ${it.episodeIds.size}" })
|
||||
onDismiss()
|
||||
|
@ -625,7 +625,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
appPrefs.edit().putBoolean(UserPreferences.Prefs.prefQueueLocked.name, locked).apply()
|
||||
dragDropEnabled = !(isQueueKeepSorted || isQueueLocked)
|
||||
refreshMenuItems()
|
||||
if (queueItems.size == 0) {
|
||||
if (queueItems.isEmpty()) {
|
||||
if (locked) (activity as MainActivity).showSnackbarAbovePlayer(R.string.queue_locked, Snackbar.LENGTH_SHORT)
|
||||
else (activity as MainActivity).showSnackbarAbovePlayer(R.string.queue_unlocked, Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
|
@ -673,7 +673,7 @@ class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
for (e in queueItems) vms.add(EpisodeVM(e))
|
||||
Logd(TAG, "loadCurQueue() curQueue.episodes: ${curQueue.episodes.size}")
|
||||
queues = realm.query(PlayQueue::class).find()
|
||||
curIndex = queues.indexOf(curQueue)
|
||||
curIndex = queues.indexOfFirst { it.id == curQueue.id }
|
||||
spinnerTexts.clear()
|
||||
spinnerTexts.addAll(queues.map { "${it.name} : ${it.size()}" })
|
||||
refreshInfoBar()
|
||||
|
|
|
@ -207,7 +207,7 @@ class QuickDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
|||
val podcast: PodcastSearchResult? = adapter.getItem(position)
|
||||
if (podcast?.feedUrl.isNullOrEmpty()) return
|
||||
|
||||
val fragment: Fragment = OnlineFeedFragment.newInstance(podcast!!.feedUrl!!)
|
||||
val fragment: Fragment = OnlineFeedFragment.newInstance(podcast.feedUrl)
|
||||
(activity as MainActivity).loadChildFragment(fragment)
|
||||
}
|
||||
|
||||
|
@ -368,7 +368,7 @@ class QuickDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
|
|||
if (result.isNotEmpty()) {
|
||||
searchResults.addAll(result)
|
||||
noResultText = ""
|
||||
} else noResultText = getString(R.string.no_results_for_query, "")
|
||||
} else noResultText = getString(R.string.no_results_for_query)
|
||||
showProgress = false
|
||||
}
|
||||
|
||||
|
|
|
@ -62,9 +62,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import java.text.NumberFormat
|
||||
|
||||
/**
|
||||
* Performs a search operation on all feeds or one specific feed and displays the search result.
|
||||
*/
|
||||
class SearchFragment : Fragment() {
|
||||
private var _binding: SearchFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -76,7 +73,7 @@ class SearchFragment : Fragment() {
|
|||
private val resultFeeds = mutableStateListOf<Feed>()
|
||||
private val results = mutableListOf<Episode>()
|
||||
private val vms = mutableStateListOf<EpisodeVM>()
|
||||
protected var infoBarText = mutableStateOf("")
|
||||
private var infoBarText = mutableStateOf("")
|
||||
|
||||
private var leftActionState = mutableStateOf<SwipeAction>(NoActionSwipeAction())
|
||||
private var rightActionState = mutableStateOf<SwipeAction>(NoActionSwipeAction())
|
||||
|
|
|
@ -119,7 +119,7 @@ class SearchResultsFragment : Fragment() {
|
|||
}
|
||||
if (searchResults.isEmpty()) Text(noResultText, color = textColor, modifier = Modifier.constrainAs(empty) { centerTo(parent) })
|
||||
if (errorText.isNotEmpty()) Text(errorText, color = textColor, modifier = Modifier.constrainAs(txtvError) { centerTo(parent) })
|
||||
if (retryQerry.isNotEmpty()) Button(modifier = Modifier.padding(16.dp).constrainAs(butRetry) { top.linkTo(txtvError.bottom)}, onClick = { search(retryQerry) }, ) {
|
||||
if (retryQerry.isNotEmpty()) Button(modifier = Modifier.padding(16.dp).constrainAs(butRetry) { top.linkTo(txtvError.bottom) }, onClick = { search(retryQerry) }) {
|
||||
Text(stringResource(id = R.string.retry_label))
|
||||
}
|
||||
Text( getString(R.string.search_powered_by, searchProvider!!.name), color = Color.Black, style = MaterialTheme.typography.labelSmall, modifier = Modifier.background(Color.LightGray)
|
||||
|
|
|
@ -367,16 +367,16 @@ class StatisticsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
private fun refreshToolbarState() {
|
||||
when (selectedTabIndex.value) {
|
||||
0 -> {
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.isVisible = true
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.isVisible = true
|
||||
}
|
||||
1 -> {
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.setVisible(true)
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.isVisible = true
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.setVisible(false)
|
||||
toolbar.menu?.findItem(R.id.statistics_reset)?.isVisible = false
|
||||
toolbar.menu?.findItem(R.id.statistics_filter)?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import android.content.ActivityNotFoundException
|
|||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
|
@ -89,6 +90,7 @@ import java.text.SimpleDateFormat
|
|||
import java.util.*
|
||||
|
||||
class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||
val prefs: SharedPreferences by lazy { requireContext().getSharedPreferences("SubscriptionsFragmentPrefs", Context.MODE_PRIVATE) }
|
||||
|
||||
private var _binding: ComposeFragmentBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
@ -97,8 +99,38 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
|
||||
private val tags: MutableList<String> = mutableListOf()
|
||||
private val queueIds: MutableList<Long> = mutableListOf()
|
||||
private var tagFilterIndex = 1
|
||||
private var queueFilterIndex = 0
|
||||
|
||||
private var _feedsFilter: String? = null
|
||||
private var feedsFilter: String
|
||||
get() {
|
||||
if (_feedsFilter == null) _feedsFilter = prefs.getString("feedsFilter", "") ?: ""
|
||||
return _feedsFilter ?: ""
|
||||
}
|
||||
set(filter) {
|
||||
_feedsFilter = filter
|
||||
prefs.edit().putString("feedsFilter", filter).apply()
|
||||
}
|
||||
|
||||
private var _tagFilterIndex: Int = -1
|
||||
private var tagFilterIndex: Int
|
||||
get() {
|
||||
if (_tagFilterIndex < 0) _tagFilterIndex = prefs.getInt("tagFilterIndex", 0)
|
||||
return _tagFilterIndex
|
||||
}
|
||||
set(index) {
|
||||
_tagFilterIndex = index
|
||||
prefs.edit().putInt("tagFilterIndex", index).apply()
|
||||
}
|
||||
private var _queueFilterIndex: Int = -1
|
||||
private var queueFilterIndex: Int
|
||||
get() {
|
||||
if (_queueFilterIndex < 0) _queueFilterIndex = prefs.getInt("queueFilterIndex", 0)
|
||||
return _queueFilterIndex
|
||||
}
|
||||
set(index) {
|
||||
_queueFilterIndex = index
|
||||
prefs.edit().putInt("queueFilterIndex", index).apply()
|
||||
}
|
||||
|
||||
private var infoTextFiltered = ""
|
||||
private var infoTextUpdate = ""
|
||||
|
@ -176,12 +208,12 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Column {
|
||||
InforBar()
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 20.dp, end = 20.dp)) {
|
||||
Spinner(items = spinnerTexts, selectedItem = spinnerTexts[0]) { index: Int ->
|
||||
Spinner(items = spinnerTexts, selectedIndex = queueFilterIndex) { index: Int ->
|
||||
queueFilterIndex = index
|
||||
loadSubscriptions()
|
||||
}
|
||||
Spacer(Modifier.weight(1f))
|
||||
Spinner(items = tags, selectedItem = tags[0]) { index: Int ->
|
||||
Spinner(items = tags, selectedIndex = tagFilterIndex) { index: Int ->
|
||||
tagFilterIndex = index
|
||||
loadSubscriptions()
|
||||
}
|
||||
|
@ -439,7 +471,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
if (selectedOption == "Custom") {
|
||||
val queues = realm.query(PlayQueue::class).find()
|
||||
Spinner(items = queues.map { it.name }, selectedItem = "Default") { index ->
|
||||
Spinner(items = queues.map { it.name }, selectedIndex = 0) { index ->
|
||||
Logd(TAG, "Queue selected: ${queues[index]}")
|
||||
saveFeedPreferences { it: FeedPreferences -> it.queueId = queues[index].id }
|
||||
onDismissRequest()
|
||||
|
@ -793,18 +825,14 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp).clickable(onClick = {
|
||||
selected.clear()
|
||||
for (i in 0..longPressIndex) {
|
||||
selected.add(feedListFiltered[i])
|
||||
}
|
||||
for (i in 0..longPressIndex) selected.add(feedListFiltered[i])
|
||||
selectedSize = selected.size
|
||||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp).clickable(onClick = {
|
||||
selected.clear()
|
||||
for (i in longPressIndex..<feedListFiltered.size) {
|
||||
selected.add(feedListFiltered[i])
|
||||
}
|
||||
for (i in longPressIndex..<feedListFiltered.size) selected.add(feedListFiltered[i])
|
||||
selectedSize = selected.size
|
||||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
|
@ -853,47 +881,37 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
private fun sortArraysFromCodeSet() {
|
||||
for (i in playStateSort.indices) {
|
||||
playStateSort[i].value = false
|
||||
}
|
||||
for (c in playStateCodeSet) {
|
||||
val e = PlayState.fromCode(c.toInt())
|
||||
playStateSort[e.ordinal].value = true
|
||||
}
|
||||
for (i in ratingSort.indices) {
|
||||
ratingSort[i].value = false
|
||||
}
|
||||
for (c in ratingCodeSet) {
|
||||
val e = Rating.fromCode(c.toInt())
|
||||
ratingSort[e.ordinal].value = true
|
||||
}
|
||||
for (i in playStateSort.indices) playStateSort[i].value = false
|
||||
for (c in playStateCodeSet) playStateSort[PlayState.fromCode(c.toInt()).ordinal].value = true
|
||||
for (i in ratingSort.indices) ratingSort[i].value = false
|
||||
for (c in ratingCodeSet) ratingSort[Rating.fromCode(c.toInt()).ordinal].value = true
|
||||
}
|
||||
|
||||
private fun saveSortingPrefs() {
|
||||
appPrefs.edit().putInt("sortIndex", sortIndex).apply()
|
||||
appPrefs.edit().putBoolean("titleAscending", titleAscending).apply()
|
||||
appPrefs.edit().putBoolean("dateAscending", dateAscending).apply()
|
||||
appPrefs.edit().putBoolean("countAscending", countAscending).apply()
|
||||
appPrefs.edit().putInt("dateSortIndex", dateSortIndex).apply()
|
||||
appPrefs.edit().putInt("downlaodedSortIndex", downlaodedSortIndex).apply()
|
||||
appPrefs.edit().putInt("commentedSortIndex", commentedSortIndex).apply()
|
||||
prefs.edit().putInt("sortIndex", sortIndex).apply()
|
||||
prefs.edit().putBoolean("titleAscending", titleAscending).apply()
|
||||
prefs.edit().putBoolean("dateAscending", dateAscending).apply()
|
||||
prefs.edit().putBoolean("countAscending", countAscending).apply()
|
||||
prefs.edit().putInt("dateSortIndex", dateSortIndex).apply()
|
||||
prefs.edit().putInt("downlaodedSortIndex", downlaodedSortIndex).apply()
|
||||
prefs.edit().putInt("commentedSortIndex", commentedSortIndex).apply()
|
||||
sortArrays2CodeSet()
|
||||
appPrefs.edit().putStringSet("playStateCodeSet", playStateCodeSet).apply()
|
||||
appPrefs.edit().putStringSet("ratingCodeSet", ratingCodeSet).apply()
|
||||
prefs.edit().putStringSet("playStateCodeSet", playStateCodeSet).apply()
|
||||
prefs.edit().putStringSet("ratingCodeSet", ratingCodeSet).apply()
|
||||
}
|
||||
|
||||
private fun getSortingPrefs() {
|
||||
sortIndex = appPrefs.getInt("sortIndex", 0)
|
||||
titleAscending = appPrefs.getBoolean("titleAscending", true)
|
||||
dateAscending = appPrefs.getBoolean("dateAscending", true)
|
||||
countAscending = appPrefs.getBoolean("countAscending", true)
|
||||
dateSortIndex = appPrefs.getInt("dateSortIndex", 0)
|
||||
downlaodedSortIndex = appPrefs.getInt("downlaodedSortIndex", -1)
|
||||
commentedSortIndex = appPrefs.getInt("commentedSortIndex", -1)
|
||||
sortIndex = prefs.getInt("sortIndex", 0)
|
||||
titleAscending = prefs.getBoolean("titleAscending", true)
|
||||
dateAscending = prefs.getBoolean("dateAscending", true)
|
||||
countAscending = prefs.getBoolean("countAscending", true)
|
||||
dateSortIndex = prefs.getInt("dateSortIndex", 0)
|
||||
downlaodedSortIndex = prefs.getInt("downlaodedSortIndex", -1)
|
||||
commentedSortIndex = prefs.getInt("commentedSortIndex", -1)
|
||||
playStateCodeSet.clear()
|
||||
playStateCodeSet.addAll(appPrefs.getStringSet("playStateCodeSet", setOf())!!)
|
||||
playStateCodeSet.addAll(prefs.getStringSet("playStateCodeSet", setOf())!!)
|
||||
ratingCodeSet.clear()
|
||||
ratingCodeSet.addAll(appPrefs.getStringSet("ratingCodeSet", setOf())!!)
|
||||
ratingCodeSet.addAll(prefs.getStringSet("ratingCodeSet", setOf())!!)
|
||||
sortArraysFromCodeSet()
|
||||
}
|
||||
|
||||
|
@ -1487,24 +1505,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
|
||||
private var prevFeedUpdatingEvent: FlowEvent.FeedUpdatingEvent? = null
|
||||
|
||||
// val feedOrderBy: Int
|
||||
// get() {
|
||||
// val value = appPrefs.getString(UserPreferences.Prefs.prefDrawerFeedOrder.name, "" + FeedSortOrder.UNPLAYED_NEW_OLD.index)
|
||||
// return value!!.toInt()
|
||||
// }
|
||||
//
|
||||
// val feedOrderDir: Int
|
||||
// get() {
|
||||
// val value = appPrefs.getInt(UserPreferences.Prefs.prefDrawerFeedOrderDir.name, 0)
|
||||
// return value
|
||||
// }
|
||||
|
||||
var feedsFilter: String
|
||||
get() = appPrefs.getString(UserPreferences.Prefs.prefFeedFilter.name, "")?:""
|
||||
set(filter) {
|
||||
appPrefs.edit().putString(UserPreferences.Prefs.prefFeedFilter.name, filter).apply()
|
||||
}
|
||||
|
||||
fun newInstance(folderTitle: String?): SubscriptionsFragment {
|
||||
val fragment = SubscriptionsFragment()
|
||||
val args = Bundle()
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.utils
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
|
||||
object LocalDeleteModal {
|
||||
|
||||
fun deleteEpisodesWarnLocal(context: Context, items: Iterable<Episode>) {
|
||||
val localItems: MutableList<Episode> = mutableListOf()
|
||||
for (item in items) {
|
||||
if (item.feed?.isLocalFeed == true) localItems.add(item)
|
||||
else deleteEpisodeMedia(context, item)
|
||||
}
|
||||
|
||||
if (localItems.isNotEmpty()) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.delete_episode_label)
|
||||
.setMessage(R.string.delete_local_feed_warning_body)
|
||||
.setPositiveButton(R.string.delete_label) { dialog: DialogInterface?, which: Int ->
|
||||
for (item in localItems) {
|
||||
deleteEpisodeMedia(context, item)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
|
||||
object PictureInPictureUtil {
|
||||
fun supportsPictureInPicture(activity: Activity): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val packageManager = activity.packageManager
|
||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
||||
} else return false
|
||||
}
|
||||
|
||||
fun isInPictureInPictureMode(activity: Activity): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && supportsPictureInPicture(activity)) activity.isInPictureInPictureMode
|
||||
else false
|
||||
}
|
||||
}
|
|
@ -79,7 +79,7 @@ class ShownotesCleaner(context: Context) {
|
|||
private fun addTimecodes(document: Document, playableDuration: Int) {
|
||||
val elementsWithTimeCodes = document.body().getElementsMatchingOwnText(TIMECODE_REGEX)
|
||||
Logd(TAG, "Recognized " + elementsWithTimeCodes.size + " timecodes")
|
||||
if (elementsWithTimeCodes.size == 0) return // No elements with timecodes
|
||||
if (elementsWithTimeCodes.isEmpty()) return // No elements with timecodes
|
||||
|
||||
var useHourFormat = true
|
||||
if (playableDuration != Int.MAX_VALUE) {
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.view
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
|
||||
class NoRelayoutTextView : AppCompatTextView {
|
||||
private var requestLayoutEnabled = false
|
||||
private var maxTextLength = 0f
|
||||
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
override fun requestLayout() {
|
||||
if (requestLayoutEnabled) super.requestLayout()
|
||||
requestLayoutEnabled = false
|
||||
}
|
||||
|
||||
override fun setText(text: CharSequence, type: BufferType) {
|
||||
val textLength = paint.measureText(text.toString())
|
||||
if (textLength > maxTextLength) {
|
||||
maxTextLength = textLength
|
||||
requestLayoutEnabled = true
|
||||
}
|
||||
super.setText(text, type)
|
||||
}
|
||||
}
|
|
@ -8,6 +8,7 @@ import android.widget.FrameLayout
|
|||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import androidx.core.util.Consumer
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class PlaybackSpeedSeekBar : FrameLayout {
|
||||
private var _binding: PlaybackSpeedSeekBarBinding? = null
|
||||
|
@ -16,6 +17,9 @@ class PlaybackSpeedSeekBar : FrameLayout {
|
|||
private lateinit var seekBar: SeekBar
|
||||
private var progressChangedListener: Consumer<Float>? = null
|
||||
|
||||
val currentSpeed: Float
|
||||
get() = (seekBar.progress + 10) / 20.0f
|
||||
|
||||
constructor(context: Context) : super(context) {
|
||||
setup()
|
||||
}
|
||||
|
@ -39,24 +43,19 @@ class PlaybackSpeedSeekBar : FrameLayout {
|
|||
val playbackSpeed = (progress + 10) / 20.0f
|
||||
progressChangedListener?.accept(playbackSpeed)
|
||||
}
|
||||
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
}
|
||||
|
||||
fun updateSpeed(speedMultiplier: Float) {
|
||||
seekBar.progress = Math.round((20 * speedMultiplier) - 10)
|
||||
seekBar.progress = ((20 * speedMultiplier) - 10).roundToInt()
|
||||
}
|
||||
|
||||
fun setProgressChangedListener(progressChangedListener: Consumer<Float>?) {
|
||||
this.progressChangedListener = progressChangedListener
|
||||
}
|
||||
|
||||
val currentSpeed: Float
|
||||
get() = (seekBar.progress + 10) / 20.0f
|
||||
|
||||
override fun setEnabled(enabled: Boolean) {
|
||||
super.setEnabled(enabled)
|
||||
seekBar.isEnabled = enabled
|
||||
|
|
|
@ -3,7 +3,6 @@ package ac.mdiq.podcini.ui.view
|
|||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.net.utils.NetworkUtils
|
||||
import ac.mdiq.podcini.storage.utils.DurationConverter
|
||||
import ac.mdiq.podcini.ui.actions.MenuItemUtils
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
|
||||
import ac.mdiq.podcini.util.*
|
||||
|
@ -142,7 +141,23 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
|
|||
menu.add(Menu.NONE, R.id.share_url_item, Menu.NONE, R.string.share_url_label)
|
||||
menu.setHeaderTitle(selectedUrl)
|
||||
}
|
||||
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem -> this.onContextItemSelected(item) }
|
||||
setOnClickListeners(menu) { item: MenuItem -> this.onContextItemSelected(item) }
|
||||
}
|
||||
|
||||
/**
|
||||
* When pressing a context menu item, Android calls onContextItemSelected
|
||||
* for ALL fragments in arbitrary order, not just for the fragment that the
|
||||
* context menu was created from. This assigns the listener to every menu item,
|
||||
* so that the correct fragment is always called first and can consume the click.
|
||||
*
|
||||
* Note that Android still calls the onContextItemSelected methods of all fragments
|
||||
* when the passed listener returns false.
|
||||
*/
|
||||
fun setOnClickListeners(menu: Menu?, listener: MenuItem.OnMenuItemClickListener?) {
|
||||
for (i in 0 until menu!!.size()) {
|
||||
if (menu.getItem(i).subMenu != null) setOnClickListeners(menu.getItem(i).subMenu, listener)
|
||||
menu.getItem(i).setOnMenuItemClickListener(listener)
|
||||
}
|
||||
}
|
||||
|
||||
fun setTimecodeSelectedListener(timecodeSelectedListener: Consumer<Int>?) {
|
||||
|
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Shota Saito
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* Source: https://github.com/shts/TriangleLabelView
|
||||
* Modified for our need; see Podcini #5925 for context
|
||||
*/
|
||||
package ac.mdiq.podcini.ui.view
|
||||
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class TriangleLabelView : View {
|
||||
private val primary = PaintHolder()
|
||||
private var topPadding = 0f
|
||||
private var bottomPadding = 0f
|
||||
private var centerPadding = 0f
|
||||
private var trianglePaint: Paint? = null
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private var corner: Corner? = null
|
||||
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : super(context, attrs, defStyleAttr) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {
|
||||
init(context, attrs)
|
||||
}
|
||||
|
||||
private fun init(context: Context, attrs: AttributeSet?) {
|
||||
val ta = context.obtainStyledAttributes(attrs, R.styleable.TriangleLabelView)
|
||||
|
||||
this.topPadding = ta.getDimension(R.styleable.TriangleLabelView_labelTopPadding, dp2px(7f).toFloat())
|
||||
this.centerPadding = ta.getDimension(R.styleable.TriangleLabelView_labelCenterPadding, dp2px(3f).toFloat())
|
||||
this.bottomPadding = ta.getDimension(R.styleable.TriangleLabelView_labelBottomPadding, dp2px(3f).toFloat())
|
||||
|
||||
val backgroundColor = ta.getColor(R.styleable.TriangleLabelView_backgroundColor, Color.parseColor("#66000000"))
|
||||
primary.color = ta.getColor(R.styleable.TriangleLabelView_primaryTextColor, Color.WHITE)
|
||||
|
||||
primary.size = ta.getDimension(R.styleable.TriangleLabelView_primaryTextSize, sp2px(11f))
|
||||
|
||||
val primary = ta.getString(R.styleable.TriangleLabelView_primaryText)
|
||||
if (primary != null) this.primary.text = primary
|
||||
|
||||
this.corner = Corner.from(ta.getInt(R.styleable.TriangleLabelView_corner, 1))
|
||||
|
||||
ta.recycle()
|
||||
|
||||
this.primary.initPaint()
|
||||
|
||||
trianglePaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
trianglePaint!!.color = backgroundColor
|
||||
|
||||
this.primary.resetStatus()
|
||||
}
|
||||
|
||||
fun setPrimaryText(text: String) {
|
||||
primary.text = text
|
||||
primary.resetStatus()
|
||||
relayout()
|
||||
}
|
||||
|
||||
fun getCorner(): Corner? {
|
||||
return corner
|
||||
}
|
||||
|
||||
fun setCorner(corner: Corner?) {
|
||||
this.corner = corner
|
||||
relayout()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
super.onDraw(canvas)
|
||||
canvas.save()
|
||||
|
||||
// translate
|
||||
canvas.translate(0f, ((height * sqrt(2.0)) - height).toFloat())
|
||||
|
||||
// rotate
|
||||
if (corner!!.left()) canvas.rotate(DEGREES_LEFT.toFloat(), 0f, height.toFloat())
|
||||
else canvas.rotate(DEGREES_RIGHT.toFloat(), width.toFloat(), height.toFloat())
|
||||
|
||||
// draw triangle
|
||||
@SuppressLint("DrawAllocation") val path = Path()
|
||||
path.moveTo(0f, height.toFloat())
|
||||
path.lineTo(width / 2f, 0f)
|
||||
path.lineTo(width.toFloat(), height.toFloat())
|
||||
path.close()
|
||||
canvas.drawPath(path, trianglePaint!!)
|
||||
|
||||
// draw primaryText
|
||||
canvas.drawText(primary.text, (width) / 2f, (topPadding + centerPadding + primary.height), primary.paint!!)
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
height = (topPadding + centerPadding + bottomPadding + primary.height).toInt()
|
||||
width = 2 * height
|
||||
val realHeight = (height * sqrt(2.0)).toInt()
|
||||
setMeasuredDimension(width, realHeight)
|
||||
}
|
||||
|
||||
fun dp2px(dpValue: Float): Int {
|
||||
val scale = context.resources.displayMetrics.density
|
||||
return (dpValue * scale + 0.5f).toInt()
|
||||
}
|
||||
|
||||
fun sp2px(spValue: Float): Float {
|
||||
val scale = context.resources.displayMetrics.scaledDensity
|
||||
return spValue * scale
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called whenever what we're displaying could have changed.
|
||||
*/
|
||||
private fun relayout() {
|
||||
invalidate()
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
enum class Corner(private val type: Int) {
|
||||
TOP_LEFT(1),
|
||||
TOP_RIGHT(2);
|
||||
|
||||
fun left(): Boolean {
|
||||
return this == TOP_LEFT
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal fun from(type: Int): Corner {
|
||||
for (c in entries) {
|
||||
if (c.type == type) return c
|
||||
}
|
||||
return TOP_LEFT
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PaintHolder {
|
||||
var text: String = ""
|
||||
var paint: Paint? = null
|
||||
var color: Int = 0
|
||||
var size: Float = 0f
|
||||
var height: Float = 0f
|
||||
var width: Float = 0f
|
||||
|
||||
fun initPaint() {
|
||||
paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||
paint!!.color = color
|
||||
paint!!.textAlign = Paint.Align.CENTER
|
||||
paint!!.textSize = size
|
||||
paint!!.setTypeface(Typeface.DEFAULT_BOLD)
|
||||
}
|
||||
|
||||
fun resetStatus() {
|
||||
val rectText = Rect()
|
||||
paint!!.getTextBounds(text, 0, text.length, rectText)
|
||||
width = rectText.width().toFloat()
|
||||
height = rectText.height().toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEGREES_LEFT = -45
|
||||
private const val DEGREES_RIGHT = 45
|
||||
}
|
||||
}
|
|
@ -36,9 +36,6 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Updates the state of the player widget.
|
||||
*/
|
||||
object WidgetUpdater {
|
||||
private val TAG: String = WidgetUpdater::class.simpleName ?: "Anonymous"
|
||||
|
||||
|
|
|
@ -11,8 +11,7 @@ import androidx.work.*
|
|||
class WidgetUpdaterWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
try {
|
||||
updateWidget()
|
||||
try { updateWidget()
|
||||
} catch (e: Exception) {
|
||||
Logd(TAG, "Failed to update Podcini widget: $e")
|
||||
return Result.failure()
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
# 6.13.11
|
||||
|
||||
* created private shared preferences for Subscriptions view and moved related properties there from the apps prefs
|
||||
* persisted settings of tag spinner and queue spinner in Subscriptions view
|
||||
* fixed again the incorrect initial text on Spinner in Queues view
|
||||
* save played duration and time spent when playback of an episode is completed
|
||||
* some code cleaning and restructuring
|
||||
* gradle update
|
||||
|
||||
# 6.13.10
|
||||
|
||||
* fixed Spinner in Subscriptions: All tags vs Untagged irregularity
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Version 6.13.11
|
||||
|
||||
* created private shared preferences for Subscriptions view and moved related properties there from the apps prefs
|
||||
* persisted settings of tag spinner and queue spinner in Subscriptions view
|
||||
* fixed again the incorrect initial text on Spinner in Queues view
|
||||
* save played duration and time spent when playback of an episode is completed
|
||||
* some code cleaning and restructuring
|
||||
* gradle update
|
|
@ -7,7 +7,7 @@ balloon = "1.6.6"
|
|||
coil = "2.7.0"
|
||||
commonsLang3 = "3.15.0"
|
||||
commonsIo = "2.16.1"
|
||||
composeBom = "2024.10.01"
|
||||
composeBom = "2024.11.00"
|
||||
conscryptAndroid = "2.5.2"
|
||||
constraintlayoutCompose = "1.1.0"
|
||||
coordinatorlayout = "1.2.0"
|
||||
|
@ -19,7 +19,7 @@ documentfile = "1.0.1"
|
|||
fyydlin = "v0.5.0"
|
||||
googleMaterialTypeface = "4.0.0.3-kotlin"
|
||||
googleMaterialTypefaceOutlined = "4.0.0.2-kotlin"
|
||||
gradle = "8.5.2"
|
||||
gradle = "8.6.1"
|
||||
gridlayout = "1.0.0"
|
||||
groovyXml = "3.0.19"
|
||||
iconicsCore = "5.5.0-b01"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#Fri Nov 15 08:33:12 WEST 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
Loading…
Reference in New Issue