6.12.0 commit

This commit is contained in:
Xilin Jia 2024-10-22 18:40:35 +01:00
parent a9f59905bd
commit 0c801717ac
112 changed files with 791 additions and 1403 deletions

View File

@ -12,7 +12,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
[<img src="./images/external/amazon.png" alt="Amazon" height="40">](https://www.amazon.com/%E8%B4%BE%E8%A5%BF%E6%9E%97-Podcini-R/dp/B0D9WR8P13)
#### Podcini.R 6.10 allows creating synthetic podcast and shelving any episdes to any synthetic podcasts
#### Podcini.R version 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Since 6.6, podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
#### Podcini.R version 6.5 as a major step forward brings YouTube contents in the app. Channels can be searched, received from share, subscribed. Podcasts, playlists as well as single media from Youtube and YT Music can be shared to Podcini. For more see the Youtube section below or the changelogs
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
#### If you need to cast to an external speaker, you should install the "play" apk, not the "free" apk, that's about the difference between the two.

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020277
versionName "6.11.7"
versionCode 3020278
versionName "6.12.0"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -106,7 +106,7 @@ class MediaPlayerBaseTest {
VolumeAdaptionSetting.OFF, null, null)
f.preferences = prefs
f.episodes.clear()
val i = Episode(0, "t", "i", "l", Date(), Episode.PlayState.UNPLAYED.code, f)
val i = Episode(0, "t", "i", "l", Date(), PlayState.UNPLAYED.code, f)
f.episodes.add(i)
val media = EpisodeMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl, fileUrl != null, null, 0, 0)
i.setMedia(media)

View File

@ -65,7 +65,7 @@ class TaskManagerTest {
val f = Feed(0, null, "title", "link", "d", null, null, null, null, "id", null, "null", "url")
f.episodes.clear()
for (i in 0 until NUM_ITEMS) {
f.episodes.add(Episode(0, pref + i, pref + i, "link", Date(), Episode.PlayState.PLAYED.code, f))
f.episodes.add(Episode(0, pref + i, pref + i, "link", Date(), PlayState.PLAYED.code, f))
}
// val adapter = getInstance()
// adapter.open()

View File

@ -3,6 +3,7 @@ package de.test.podcini.ui
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.PlayState
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import android.content.Context
@ -113,7 +114,7 @@ class UITestUtils(private val context: Context) {
val items: MutableList<Episode> = ArrayList()
for (j in 0 until NUM_ITEMS_PER_FEED) {
val item = Episode(j.toLong(), "Feed " + (i + 1) + ": Item " + (j + 1), "item$j",
"http://example.com/feed$i/item/$j", Date(), Episode.PlayState.UNPLAYED.code, feed)
"http://example.com/feed$i/item/$j", Date(), PlayState.UNPLAYED.code, feed)
items.add(item)
if (!hostTextOnlyFeeds) {

View File

@ -362,7 +362,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
.addTag(WORK_TAG_EPISODE_URL + item.media!!.downloadUrl)
if (enqueueDownloadedEpisodes()) {
if (item.feed?.preferences?.queue != null)
runBlocking { Queues.addToQueueSync(false, item, item.feed?.preferences?.queue) }
runBlocking { Queues.addToQueueSync(item, item.feed?.preferences?.queue) }
workRequest.addTag(WORK_DATA_WAS_QUEUED)
}
workRequest.setInputData(Data.Builder().putLong(WORK_DATA_MEDIA_ID, item.media!!.id).build())

View File

@ -121,7 +121,7 @@ object LocalFeedUpdater {
}
private fun createFeedItem(feed: Feed, file: FastDocumentFile, context: Context): Episode {
val item = Episode(0L, file.name, UUID.randomUUID().toString(), file.name, Date(file.lastModified), Episode.PlayState.UNPLAYED.code, feed)
val item = Episode(0L, file.name, UUID.randomUUID().toString(), file.name, Date(file.lastModified), PlayState.UNPLAYED.code, feed)
item.disableAutoDownload()
val size = file.length
val media = EpisodeMedia(0, item, 0, 0, size, file.type,

View File

@ -82,9 +82,7 @@ object InTheatre {
}
upsert(curQueue_) {}
}
upsert(curQueue) {
it.update()
}
upsert(curQueue) { it.update() }
}
Logd(TAG, "starting curState")
@ -135,10 +133,12 @@ object InTheatre {
val type = curState.curMediaType.toInt()
if (type == EpisodeMedia.PLAYABLE_TYPE_FEEDMEDIA) {
val mediaId = curState.curMediaId
Logd(TAG, "loadPlayableFromPreferences getting mediaId: $mediaId")
if (mediaId != 0L) {
curMedia = getEpisodeMedia(mediaId)
if (curEpisode != null) curEpisode = (curMedia as EpisodeMedia).episodeOrFetch()
}
Logd(TAG, "loadPlayableFromPreferences: curMedia: ${curMedia?.getIdentifier()}")
} else Log.e(TAG, "Could not restore Playable object from preferences")
}
}

View File

@ -393,7 +393,7 @@ 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(Episode.PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!)
item = setPlayStateSync(PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!)
val action = item?.feed?.preferences?.autoDeleteAction
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
@ -1189,7 +1189,7 @@ class PlaybackService : MediaLibraryService() {
if (media != null) {
media.setPosition(position)
media.setLastPlayedTime(System.currentTimeMillis())
if (it.isNew) it.playState = Episode.PlayState.UNPLAYED.code
if (it.isNew) it.playState = PlayState.UNPLAYED.code
if (media.startPosition >= 0 && media.getPosition() > media.startPosition)
media.playedDuration = (media.playedDurationWhenStarted + media.getPosition() - media.startPosition)
}

View File

@ -12,6 +12,7 @@ import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
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.storage.model.PlayState
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.util.Log
@ -182,7 +183,7 @@ object AutoCleanups {
val idsInQueues = getInQueueEpisodeIds()
val mostRecentDateForDeletion = calcMostRecentDateForDeletion(Date())
for (item in downloadedItems) {
if (item.media != null && item.media!!.downloaded && !idsInQueues.contains(item.id) && item.isPlayed() && !item.isFavorite) {
if (item.media != null && item.media!!.downloaded && !idsInQueues.contains(item.id) && item.playState >= PlayState.PLAYED.code && !item.isFavorite) {
val media = item.media
// make sure this candidate was played at least the proper amount of days prior to now
if (media?.playbackCompletionDate != null && media.playbackCompletionDate!!.before(mostRecentDateForDeletion)) candidates.add(item)

View File

@ -18,11 +18,7 @@ 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.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Episode.PlayState
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor
import ac.mdiq.podcini.storage.utils.FilesUtils.getMediafilename
import ac.mdiq.podcini.util.EventFlow
@ -287,7 +283,7 @@ object Episodes {
var episode_ = episode
if (!episode.isManaged()) episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find() ?: episode
val result = upsert(episode_) {
if (played >= PlayState.NEW.code && played <= PlayState.BUILDING.code) it.playState = played
if (played != PlayState.UNSPECIFIED.code) it.playState = played
else {
if (it.playState == PlayState.PLAYED.code) it.playState = PlayState.UNPLAYED.code
else it.playState = PlayState.PLAYED.code

View File

@ -287,6 +287,7 @@ object Feeds {
episode.feed = savedFeed
episode.id = idLong++
episode.feedId = savedFeed.id
episode.playState = PlayState.NEW.code
if (episode.media != null) {
episode.media!!.id = episode.id
if (!savedFeed.hasVideoMedia && episode.media!!.getMediaType() == MediaType.VIDEO) savedFeed.hasVideoMedia = true
@ -300,7 +301,7 @@ object Feeds {
episode.setNew()
if (savedFeed.preferences?.autoAddNewToQueue == true) {
val q = savedFeed.preferences?.queue
if (q != null) runOnIOScope { addToQueueSync(false, episode, q) }
if (q != null) runOnIOScope { addToQueueSync(episode, q) }
}
}
}

View File

@ -56,7 +56,7 @@ object LogsAndStats {
feedTotalTime += m.duration
if (m.lastPlayedTime in timeFilterFrom..<timeFilterTo) {
if (includeMarkedAsPlayed) {
if ((m.playbackCompletionTime > 0 && m.playedDuration > 0) || m.episodeOrFetch()?.playState == Episode.PlayState.PLAYED.code || m.position > 0) {
if ((m.playbackCompletionTime > 0 && m.playedDuration > 0) || m.episodeOrFetch()?.playState == PlayState.PLAYED.code || m.position > 0) {
episodesStarted += 1
feedPlayedTime += m.duration
}

View File

@ -6,6 +6,7 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert
@ -77,9 +78,9 @@ object Queues {
appPrefs.edit().putString(UserPreferences.Prefs.prefEnqueueLocation.name, location.name).apply()
}
fun queueFromName(name: String): PlayQueue? {
return realm.query(PlayQueue::class).query("name == $0", name).first().find()
}
// fun queueFromName(name: String): PlayQueue? {
// return realm.query(PlayQueue::class).query("name == $0", name).first().find()
// }
fun getInQueueEpisodeIds(): Set<Long> {
Logd(TAG, "getQueueIDList() called")
@ -98,13 +99,13 @@ object Queues {
* @param episodes the Episode objects that should be added to the queue.
*/
@UnstableApi @JvmStatic @Synchronized
fun addToQueue(markAsUnplayed: Boolean, vararg episodes: Episode) : Job {
fun addToQueue(vararg episodes: Episode) : Job {
Logd(TAG, "addToQueue( ... ) called")
return runOnIOScope {
if (episodes.isEmpty()) return@runOnIOScope
var queueModified = false
val markAsUnplayeds = mutableListOf<Episode>()
val setInQueue = mutableListOf<Episode>()
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
val updatedItems: MutableList<Episode> = ArrayList()
val positionCalculator = EnqueuePositionPolicy(enqueueLocation)
@ -121,7 +122,7 @@ object Queues {
updatedItems.add(episode)
qItems.add(insertPosition, episode)
queueModified = true
if (episode.isNew) markAsUnplayeds.add(episode)
if (episode.playState < PlayState.INQUEUE.code) setInQueue.add(episode)
insertPosition++
}
if (queueModified) {
@ -134,13 +135,13 @@ object Queues {
}
for (event in events) EventFlow.postEvent(event)
if (markAsUnplayed && markAsUnplayeds.size > 0) setPlayState(Episode.PlayState.UNPLAYED.code, false, *markAsUnplayeds.toTypedArray())
setPlayState(PlayState.INQUEUE.code, false, *setInQueue.toTypedArray())
// if (performAutoDownload) autodownloadEpisodeMedia(context)
}
}
}
suspend fun addToQueueSync(markAsUnplayed: Boolean, episode: Episode, queue_: PlayQueue? = null) {
suspend fun addToQueueSync(episode: Episode, queue_: PlayQueue? = null) {
Logd(TAG, "addToQueueSync( ... ) called")
val queue = queue_ ?: curQueue
if (queue.episodeIds.contains(episode.id)) return
@ -157,7 +158,7 @@ object Queues {
}
if (queue.id == curQueue.id) curQueue = queueNew
if (markAsUnplayed && episode.isNew) setPlayState(Episode.PlayState.UNPLAYED.code, false, episode)
if (episode.playState < PlayState.INQUEUE.code) setPlayState(PlayState.INQUEUE.code, false, episode)
if (queue.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
// if (performAutoDownload) autodownloadEpisodeMedia(context)
}
@ -194,6 +195,9 @@ object Queues {
it.episodeIds.clear()
it.update()
}
for (e in curQueue.episodes) {
if (e.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, e)
}
curQueue.episodes.clear()
EventFlow.postEvent(FlowEvent.QueueEvent.cleared())
}
@ -230,7 +234,7 @@ object Queues {
var queue = queue_ ?: curQueue
if (queue.size() == 0) return
val events: MutableList<FlowEvent.QueueEvent> = ArrayList()
val events: MutableList<FlowEvent.QueueEvent> = mutableListOf()
val indicesToRemove: MutableList<Int> = mutableListOf()
val qItems = queue.episodes.toMutableList()
val eList = episodes.toList()
@ -239,6 +243,7 @@ object Queues {
if (indexOfItemWithId(eList, episode.id) >= 0) {
Logd(TAG, "removing from queue: ${episode.id} ${episode.title}")
indicesToRemove.add(i)
if (episode.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, episode)
if (queue.id == curQueue.id) events.add(FlowEvent.QueueEvent.removed(episode))
}
}
@ -270,6 +275,10 @@ object Queues {
if (q.size() == 0 || q.id == curQueue.id) continue
idsInQueuesToRemove = q.episodeIds.intersect(episodeIds.toSet()).toMutableSet()
if (idsInQueuesToRemove.isNotEmpty()) {
val eList = realm.query(Episode::class).query("id IN $0", idsInQueuesToRemove).find()
for (e in eList) {
if (e.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, e)
}
upsert(q) {
it.idsBinList.removeAll(idsInQueuesToRemove)
it.idsBinList.addAll(idsInQueuesToRemove)
@ -288,6 +297,10 @@ object Queues {
}
idsInQueuesToRemove = q.episodeIds.intersect(episodeIds.toSet()).toMutableSet()
if (idsInQueuesToRemove.isNotEmpty()) {
val eList = realm.query(Episode::class).query("id IN $0", idsInQueuesToRemove).find()
for (e in eList) {
if (e.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, e)
}
curQueue = upsert(q) {
it.idsBinList.removeAll(idsInQueuesToRemove)
it.idsBinList.addAll(idsInQueuesToRemove)

View File

@ -40,7 +40,7 @@ object RealmDB {
SubscriptionLog::class,
Chapter::class))
.name("Podcini.realm")
.schemaVersion(27)
.schemaVersion(28)
.migration({ mContext ->
val oldRealm = mContext.oldRealm // old realm using the previous schema
val newRealm = mContext.newRealm // new realm using the new schema
@ -104,8 +104,22 @@ object RealmDB {
// )
// }
}
})
.build()
if (oldRealm.schemaVersion() < 28) {
Logd(TAG, "migrating DB from below 27")
mContext.enumerate(className = "Episode") { oldObject: DynamicRealmObject, newObject: DynamicMutableRealmObject? ->
newObject?.run {
if (oldObject.getValue<Long>(fieldName = "playState") == 1L) {
set("playState", 10L)
} else {
val media = oldObject.getObject(propertyName = "media")
var position = 0L
if (media != null) position = media.getValue(propertyName = "position", Long::class) ?: 0
if (position > 0) set("playState", 5L)
}
}
}
}
}).build()
realm = Realm.open(config)
}

View File

@ -86,7 +86,7 @@ class Episode : RealmObject {
var rating: Int = Rating.UNRATED.code
@Ignore
var isFavorite: Boolean = (rating == 2)
var isFavorite: Boolean = (rating == Rating.FAVORITE.code)
private set
var comment: String = ""
@ -142,7 +142,7 @@ class Episode : RealmObject {
val isRemote = mutableStateOf(false)
constructor() {
this.playState = PlayState.UNPLAYED.code
this.playState = PlayState.NEW.code
}
/**
@ -313,19 +313,10 @@ class Episode : RealmObject {
return result
}
fun shiftRating(): Int {
val nr = rating + 1
return if (nr <= Rating.FAVORITE.code) nr else Rating.TRASH.code
}
enum class PlayState(val code: Int) {
UNSPECIFIED(-2),
NEW(-1),
UNPLAYED(0),
PLAYED(1),
BUILDING(2),
ABANDONED(3)
}
// fun shiftRating(): Int {
// val nr = rating + 1
// return if (nr <= Rating.FAVORITE.code) nr else Rating.TRASH.code
// }
companion object {
val TAG: String = Episode::class.simpleName ?: "Anonymous"

View File

@ -38,8 +38,8 @@ class EpisodeFilter(vararg properties: String) : Serializable {
fun matches(item: Episode): Boolean {
when {
showNew && !item.isNew -> return false
showPlayed && !item.isPlayed() -> return false
showUnplayed && item.isPlayed() -> return false
showPlayed && item.playState < PlayState.PLAYED.code -> return false
showUnplayed && item.playState >= PlayState.PLAYED.code -> return false
showPaused && !item.isInProgress -> return false
showNotPaused && item.isInProgress -> return false
showDownloaded && !item.isDownloaded -> return false
@ -58,18 +58,18 @@ class EpisodeFilter(vararg properties: String) : Serializable {
// filter on queues does not have a query string so it's not applied on query results, need to filter separately
fun matchesForQueues(item: Episode): Boolean {
when {
showQueued && !inAnyQueue(item) -> return false
showNotQueued && inAnyQueue(item) -> return false
else -> return true
}
return when {
showQueued && !inAnyQueue(item) -> false
showNotQueued && inAnyQueue(item) -> false
else -> true
}
}
fun queryString(): String {
val statements: MutableList<String> = ArrayList()
when {
showPlayed -> statements.add("playState == 1 ")
showUnplayed -> statements.add(" playState != 1 ") // Match "New" items (read = -1) as well
showPlayed -> statements.add("playState >= ${PlayState.PLAYED.code}")
showUnplayed -> statements.add(" playState < ${PlayState.PLAYED.code}> ") // Match "New" items (read = -1) as well
showNew -> statements.add("playState == -1 ")
}
when {
@ -127,8 +127,8 @@ class EpisodeFilter(vararg properties: String) : Serializable {
auto_downloadable,
not_auto_downloadable
}
companion object {
companion object {
@JvmStatic
fun unfiltered(): EpisodeFilter {
return EpisodeFilter("")

View File

@ -283,7 +283,7 @@ class Feed : RealmObject {
}
fun getVirtualQueueItems(): List<Episode> {
var qString = "feedId == $id AND playState != ${Episode.PlayState.PLAYED.code}"
var qString = "feedId == $id AND playState != ${PlayState.PLAYED.code}"
// TODO: perhaps need to set prefStreamOverDownload for youtube feeds
if (type != FeedType.YOUTUBE.name && preferences?.prefStreamOverDownload != true) qString += " AND media.downloaded == true"
val eList_ = realm.query(Episode::class, qString).find().toMutableList()

View File

@ -0,0 +1,23 @@
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.R
enum class PlayState(val code: Int, val res: Int, val userSet: Boolean) {
UNSPECIFIED(-10, R.drawable.ic_questionmark, false),
BUILDING(-2, R.drawable.baseline_build_24, false),
NEW(-1, R.drawable.baseline_fiber_new_24, false),
UNPLAYED(0, R.drawable.baseline_new_label_24, true),
LATER(1, R.drawable.baseline_watch_later_24, true),
SOON(2, R.drawable.baseline_local_play_24, true),
INQUEUE(3, R.drawable.ic_playlist_play_black, false),
INPROGRESS(5, R.drawable.baseline_play_circle_outline_24, false),
SKIPPED(6, R.drawable.ic_skip_24dp, true),
PLAYED(10, R.drawable.ic_mark_played, true), // was 1
IGNORED(20, R.drawable.baseline_visibility_off_24, true);
companion object {
fun fromCode(code: Int): PlayState {
return enumValues<PlayState>().firstOrNull { it.code == code } ?: UNSPECIFIED
}
}
}

View File

@ -5,16 +5,16 @@ import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.net.utils.NetworkUtils
import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.playback.base.InTheatre.isCurrentlyPlaying
import ac.mdiq.podcini.playback.base.VideoMode
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.getPlayerActivityIntent
import ac.mdiq.podcini.preferences.UsageStatistics
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.preferences.UserPreferences.videoPlayMode
import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.RealmDB
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.AudioMediaTools
@ -36,15 +36,19 @@ import android.widget.Toast
import androidx.annotation.DrawableRes
import androidx.annotation.OptIn
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
@ -153,7 +157,7 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
class VisitWebsiteActionButton(item: Episode) : EpisodeActionButton(item) {
override val visibility: Boolean
get() = if (item.link.isNullOrEmpty()) false else true
get() = !item.link.isNullOrEmpty()
override fun getLabel(): Int {
return R.string.visit_website_label
@ -222,6 +226,7 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) {
} else {
PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).callEvenIfRunning(true).start()
item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) }
EventFlow.postEvent(FlowEvent.PlayEvent(item))
}
playVideoIfNeeded(context, media)
@ -253,8 +258,7 @@ class DeleteActionButton(item: Episode) : EpisodeActionButton(item) {
override val visibility: Boolean
get() {
if (item.media != null && (item.media!!.downloaded || item.feed?.isLocalFeed == true)) return true
return false
return item.media != null && (item.media!!.downloaded || item.feed?.isLocalFeed == true)
}
override fun getLabel(): Int {
@ -291,7 +295,7 @@ class PauseActionButton(item: Episode) : EpisodeActionButton(item) {
class DownloadActionButton(item: Episode) : EpisodeActionButton(item) {
override val visibility: Boolean
get() = if (item.feed?.isLocalFeed == true) false else true
get() = item.feed?.isLocalFeed != true
override fun getLabel(): Int {
return R.string.download_label
@ -371,7 +375,10 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) {
fun stream(context: Context, media: Playable) {
if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start()
if (media is EpisodeMedia && media.episode != null) EventFlow.postEvent(FlowEvent.PlayEvent(media.episode!!))
if (media is EpisodeMedia && media.episode != null) {
val item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, media.episode!!) }
EventFlow.postEvent(FlowEvent.PlayEvent(item))
}
playVideoIfNeeded(context, media)
}
}
@ -382,7 +389,7 @@ class TTSActionButton(item: Episode) : EpisodeActionButton(item) {
private var readerText: String? = null
override val visibility: Boolean
get() = if (item.link.isNullOrEmpty()) false else true
get() = !item.link.isNullOrEmpty()
override fun getLabel(): Int {
return R.string.TTS_label
@ -535,6 +542,7 @@ class PlayLocalActionButton(item: Episode) : EpisodeActionButton(item) {
} else {
PlaybackService.clearCurTempSpeed()
PlaybackServiceStarter(context, media).callEvenIfRunning(true).start()
item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) }
EventFlow.postEvent(FlowEvent.PlayEvent(item))
}
if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context,
@ -542,23 +550,3 @@ class PlayLocalActionButton(item: Episode) : EpisodeActionButton(item) {
actionState.value = getLabel()
}
}
class MarkAsPlayedActionButton(item: Episode) : EpisodeActionButton(item) {
override val visibility: Boolean
get() = if (item.isPlayed()) false else true
override fun getLabel(): Int {
return (if (item.media != null) R.string.mark_read_label else R.string.mark_read_no_media_label)
}
override fun getDrawable(): Int {
return R.drawable.ic_check
}
@UnstableApi
override fun onClick(context: Context) {
if (!item.isPlayed()) Episodes.setPlayState(Episode.PlayState.PLAYED.code, true, item)
actionState.value = getLabel()
}
}

View File

@ -29,6 +29,7 @@ interface SwipeAction {
START_DOWNLOAD,
MARK_FAV,
TOGGLE_PLAYED,
SET_PLAY_STATE,
REMOVE_FROM_QUEUE,
DELETE,
REMOVE_FROM_HISTORY

View File

@ -1,10 +1,10 @@
package ac.mdiq.podcini.ui.actions
//import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.R
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Queues.addToQueue
@ -16,21 +16,20 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.PlayState
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes.NO_ACTION
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.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.PlayStateDialog
import ac.mdiq.podcini.ui.fragment.*
//import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
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.SharedPreferences
import android.os.Handler
import android.util.TypedValue
import android.view.ViewGroup
import androidx.annotation.OptIn
@ -47,7 +46,6 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
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.style.TextAlign
@ -147,7 +145,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
@OptIn(UnstableApi::class)
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) {
addToQueue( true, item)
addToQueue(item)
}
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
@ -398,7 +396,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
fun addToQueueAt(episode: Episode, index: Int) : Job {
return runOnIOScope {
if (curQueue.episodeIds.contains(episode.id)) return@runOnIOScope
if (episode.isNew) setPlayState(Episode.PlayState.UNPLAYED.code, false, episode)
if (episode.isNew) setPlayState(PlayState.UNPLAYED.code, false, episode)
curQueue = upsert(curQueue) {
it.episodeIds.add(index, episode.id)
it.update()
@ -438,9 +436,9 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
}
}
class TogglePlaybackStateSwipeAction : SwipeAction {
class SetPlaybackStateSwipeAction : SwipeAction {
override fun getId(): String {
return ActionTypes.TOGGLE_PLAYED.name
return ActionTypes.SET_PLAY_STATE.name
}
override fun getActionIcon(): Int {
@ -452,45 +450,22 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
}
override fun getTitle(context: Context): String {
return context.getString(R.string.toggle_played_label)
return context.getString(R.string.set_play_state_label)
}
override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) {
val newState = if (item.playState == Episode.PlayState.UNPLAYED.code) Episode.PlayState.PLAYED.code else Episode.PlayState.UNPLAYED.code
Logd("TogglePlaybackStateSwipeAction", "performAction( ${item.id} )")
// we're marking it as unplayed since the user didn't actually play it
// but they don't want it considered 'NEW' anymore
var item = runBlocking { setPlayStateSync(newState, false, item) }
val h = Handler(fragment.requireContext().mainLooper)
val r = Runnable {
val media: EpisodeMedia? = item.media
val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!)
if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) {
item = deleteMediaSync(fragment.requireContext(), item)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item) }
}
val playStateStringRes: Int = when (newState) {
Episode.PlayState.UNPLAYED.code -> if (item.playState == Episode.PlayState.NEW.code) R.string.removed_inbox_label //was new
else R.string.marked_as_unplayed_label //was played
Episode.PlayState.PLAYED.code -> R.string.marked_as_played_label
else -> if (item.playState == Episode.PlayState.NEW.code) R.string.removed_inbox_label
else R.string.marked_as_unplayed_label
}
val duration: Int = Snackbar.LENGTH_LONG
if (willRemove(filter, item)) {
(fragment.activity as MainActivity).showSnackbarAbovePlayer(
playStateStringRes, duration)
.setAction(fragment.getString(R.string.undo)) {
setPlayState(item.playState, false, item)
// don't forget to cancel the thing that's going to remove the media
h.removeCallbacks(r)
var showPlayStateDialog by mutableStateOf(true)
val composeView = ComposeView(fragment.requireContext()).apply {
setContent {
CustomTheme(fragment.requireContext()) {
if (showPlayStateDialog) PlayStateDialog(listOf(item)) {
showPlayStateDialog = false
(fragment.view as? ViewGroup)?.removeView(this@apply)
}
}
}
}
h.postDelayed(r, ceil((duration * 1.05f).toDouble()).toLong())
(fragment.view as? ViewGroup)?.addView(composeView)
}
private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking {
@ -504,7 +479,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
}
override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean {
return if (item.playState == Episode.PlayState.NEW.code) filter.showPlayed || filter.showNew
return if (item.playState == PlayState.NEW.code) filter.showPlayed || filter.showNew
else filter.showUnplayed || filter.showPlayed || filter.showNew
}
}
@ -523,7 +498,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
val swipeActions: List<SwipeAction> = listOf(
NoActionSwipeAction(), ComboSwipeAction(), AddToQueueSwipeAction(),
StartDownloadSwipeAction(), SetRatingSwipeAction(),
TogglePlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(),
SetPlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(),
DeleteSwipeAction(), RemoveFromHistorySwipeAction())
private fun getPrefs(tag: String, defaultActions: String): Actions {

View File

@ -633,6 +633,10 @@ class MainActivity : CastEnabledActivity() {
}
}
fun openDrawer() {
drawerLayout?.openDrawer(navDrawer)
}
private var eventSink: Job? = null
private var eventStickySink: Job? = null
private fun cancelFlowEvents() {

View File

@ -3,18 +3,27 @@ package ac.mdiq.podcini.ui.compose
import ac.mdiq.podcini.R
import ac.mdiq.podcini.net.download.DownloadStatus
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync
import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem
import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
@ -22,6 +31,7 @@ import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.Feed.Companion.MAX_SYNTHETIC_ID
import ac.mdiq.podcini.storage.model.Feed.Companion.newId
import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.EpisodeActionButton
import ac.mdiq.podcini.ui.actions.EpisodeActionButton.Companion.forItem
@ -121,7 +131,7 @@ var queueChanged by mutableIntStateOf(0)
@Stable
class EpisodeVM(var episode: Episode) {
var positionState by mutableStateOf(episode.media?.position?:0)
var playedState by mutableStateOf(episode.isPlayed())
var playedState by mutableIntStateOf(episode.playState)
var isPlayingState by mutableStateOf(false)
var ratingState by mutableIntStateOf(episode.rating)
var inProgressState by mutableStateOf(episode.isInProgress)
@ -157,9 +167,9 @@ class EpisodeVM(var episode: Episode) {
Logd("EpisodeVM", "episodeMonitor UpdatedObject ${changes.obj.title} ${changes.changedFields.joinToString()}")
if (episode.id == changes.obj.id) {
withContext(Dispatchers.Main) {
playedState = changes.obj.isPlayed()
playedState = changes.obj.playState
ratingState = changes.obj.rating
// episode = changes.obj // direct assignment doesn't update member like media??
episode = changes.obj // direct assignment doesn't update member like media??
}
Logd("EpisodeVM", "episodeMonitor $playedState $playedState ")
} else Logd("EpisodeVM", "episodeMonitor index out bound")
@ -183,7 +193,7 @@ class EpisodeVM(var episode: Episode) {
positionState = changes.obj.media?.position ?: 0
inProgressState = changes.obj.isInProgress
Logd("EpisodeVM", "mediaMonitor $positionState $inProgressState ${episode.title}")
// episode = changes.obj
episode = changes.obj
// Logd("EpisodeVM", "mediaMonitor downloaded: ${changes.obj.media?.downloaded} ${episode.media?.downloaded}")
}
} else Logd("EpisodeVM", "mediaMonitor index out bound")
@ -231,6 +241,61 @@ fun ChooseRatingDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
}
}
@Composable
fun PlayStateDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
val context = LocalContext.current
Dialog(onDismissRequest = onDismissRequest) {
Surface(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
for (state in PlayState.entries) {
if (state.userSet) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp)
.clickable {
for (item in selected) {
var item_ = runBlocking { setPlayStateSync(state.code, false, item) }
val media: EpisodeMedia? = item_.media
val shouldAutoDelete = if (item_.feed == null) false else shouldAutoDeleteItem(item_.feed!!)
if (media != null && hasAlmostEnded(media) && shouldAutoDelete) {
item_ = deleteMediaSync(context, item_)
if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_)
}
when (state) {
PlayState.UNPLAYED -> {
if (isProviderConnected && item_?.feed?.isLocalFeed != true && item_?.media != null) {
val actionNew: EpisodeAction = EpisodeAction.Builder(item_!!, EpisodeAction.NEW).currentTimestamp().build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, actionNew)
}
}
PlayState.PLAYED -> {
if (item_?.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) {
val media: EpisodeMedia? = item_?.media
// not all items have media, Gpodder only cares about those that do
if (isProviderConnected && media != null) {
val actionPlay: EpisodeAction = EpisodeAction.Builder(item_!!, EpisodeAction.PLAY)
.currentTimestamp()
.started(media.getDuration() / 1000)
.position(media.getDuration() / 1000)
.total(media.getDuration() / 1000)
.build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(context, actionPlay)
}
}
}
else -> {}
}
}
onDismissRequest()
}) {
Icon(imageVector = ImageVector.vectorResource(id = state.res), "")
Text(state.name, Modifier.padding(start = 4.dp))
}
}
}
}
}
}
}
@Composable
fun PutToQueueDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
val queues = realm.query(PlayQueue::class).find()
@ -271,7 +336,7 @@ fun PutToQueueDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
}
selected.forEach { e ->
runBlocking { addToQueueSync(false, e, toQueue) }
runBlocking { addToQueueSync(e, toQueue) }
}
onDismissRequest()
}) {
@ -366,6 +431,9 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(selected) { showChooseRatingDialog = false }
var showPlayStateDialog by remember { mutableStateOf(false) }
if (showPlayStateDialog) PlayStateDialog(selected) { showPlayStateDialog = false }
var showPutToQueueDialog by remember { mutableStateOf(false) }
if (showPutToQueueDialog) PutToQueueDialog(selected) { showPutToQueueDialog = false }
@ -460,13 +528,14 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
{ Row(modifier = Modifier
.padding(horizontal = 16.dp)
.clickable {
showPlayStateDialog = true
isExpanded = false
selectMode = false
Logd(TAG, "ic_mark_played: ${selected.size}")
setPlayState(Episode.PlayState.UNSPECIFIED.code, false, *selected.toTypedArray())
// setPlayState(PlayState.UNSPECIFIED.code, false, *selected.toTypedArray())
}, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_mark_played), "Toggle played state")
Text(stringResource(id = R.string.toggle_played_label)) } },
Text(stringResource(id = R.string.set_play_state_label)) } },
{ Row(modifier = Modifier
.padding(horizontal = 16.dp)
.clickable {
@ -483,7 +552,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
isExpanded = false
selectMode = false
Logd(TAG, "ic_playlist_play: ${selected.size}")
Queues.addToQueue(true, *selected.toTypedArray())
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)) } },
@ -520,8 +589,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
)
if (selected.isNotEmpty() && selected[0].isRemote.value)
options.add {
Row(modifier = Modifier
.padding(horizontal = 16.dp)
Row(modifier = Modifier.padding(horizontal = 16.dp)
.clickable {
isExpanded = false
selectMode = false
@ -594,7 +662,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
val (imgvCover, checkMark) = createRefs()
val imgLoc = remember(vm) { ImageResourceUtils.getEpisodeListImageLocation(vm.episode) }
Logd(TAG, "imgLoc: $imgLoc")
// Logd(TAG, "imgLoc: $imgLoc")
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
contentDescription = "imgvCover",
@ -609,8 +677,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
if (selectMode) toggleSelected()
else if (vm.episode.feed != null) activity.loadChildFragment(FeedInfoFragment.newInstance(vm.episode.feed!!))
}))
val alpha = if (vm.playedState) 1.0f else 0f
if (vm.playedState) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_check), tint = textColor, contentDescription = "played_mark",
val alpha = if (vm.playedState >= PlayState.SKIPPED.code) 1.0f else 0f
if (vm.playedState >= PlayState.SKIPPED.code) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_check), tint = textColor, contentDescription = "played_mark",
modifier = Modifier.background(Color.Green).alpha(alpha)
.constrainAs(checkMark) {
bottom.linkTo(parent.bottom)
@ -640,14 +708,16 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
vms[index].inQueueState = curQueue.contains(vms[index].episode)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Logd(TAG, "info row")
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(14.dp).height(14.dp))
// Logd(TAG, "info row")
val ratingIconRes = Rating.fromCode(vm.ratingState).res
if (vm.ratingState != Rating.UNRATED.code)
Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating", modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(14.dp).height(14.dp))
val playStateRes = PlayState.fromCode(vm.playedState).res
Icon(imageVector = ImageVector.vectorResource(playStateRes), tint = textColor, contentDescription = "playState", modifier = Modifier.width(14.dp).height(14.dp))
if (vm.inQueueState)
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", modifier = Modifier.width(14.dp).height(14.dp))
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(14.dp).height(14.dp))
val curContext = LocalContext.current
val dur = remember { vm.episode.media?.getDuration() ?: 0 }
val durText = remember { DurationConverter.getDurationStringLong(dur) }
@ -681,7 +751,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed?
detectTapGestures(onLongPress = { vms[index].showAltActionsDialog = true },
onTap = { vms[index].actionButton.onClick(activity) })
}, ) {
Logd(TAG, "button box")
// Logd(TAG, "button box")
vm.actionRes = vm.actionButton.getDrawable()
Icon(imageVector = ImageVector.vectorResource(vm.actionRes), tint = textColor, contentDescription = null, modifier = Modifier.width(28.dp).height(32.dp))
if (isDownloading() && vm.dlPercent >= 0) CircularProgressIndicator(progress = { 0.01f * vm.dlPercent },

View File

@ -6,6 +6,8 @@ import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.ServiceStatusHandler
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.playback.base.InTheatre.isCurrentlyPlaying
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.VideoMode
@ -85,11 +87,8 @@ import coil.request.CachePolicy
import coil.request.ImageRequest
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.dankito.readability4j.Readability4J
import org.apache.commons.lang3.StringUtils
import java.text.DecimalFormat
@ -108,6 +107,7 @@ class AudioPlayerFragment : Fragment() {
private var prevItem: Episode? = null
private var currentItem: Episode? = null
private var playButInit = false
private var isShowPlay: Boolean = true
private var showTimeLeft = false
@ -166,7 +166,10 @@ class AudioPlayerFragment : Fragment() {
}
}
}
(activity as MainActivity).setPlayerVisible(false)
Logd(TAG, "curMedia: ${curMedia?.getIdentifier()}")
(activity as MainActivity).setPlayerVisible(curMedia != null)
if (curMedia != null) updateUi(curMedia!!)
// if (curMedia is EpisodeMedia) setIsShowPlay(isCurrentlyPlaying(curMedia as EpisodeMedia))
return composeView
}
@ -188,7 +191,7 @@ class AudioPlayerFragment : Fragment() {
if (curMedia == null) return
if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start()
}
val imgLoc_ = remember(currentItem) { imgLoc }
val imgLoc_ = remember(currentMedia) { imgLoc }
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc_)
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
contentDescription = "imgvCover",
@ -502,9 +505,15 @@ class AudioPlayerFragment : Fragment() {
txtvPlaybackSpeed = speedStr
// binding.butPlaybackSpeed.setSpeed(event.newSpeed) TODO
}
@UnstableApi
fun onPositionUpdate(event: FlowEvent.PlaybackPositionEvent) {
Logd(TAG, "onPositionUpdate")
if (!playButInit && playButRes == R.drawable.ic_play_48dp && curMedia is EpisodeMedia) {
playButRes = R.drawable.ic_pause
playButInit = true
}
if (curMedia?.getIdentifier() != event.media?.getIdentifier() || controller == null || curPositionFB == Playable.INVALID_TIME || curDurationFB == Playable.INVALID_TIME) return
val converter = TimeSpeedConverter(curSpeedFB)
currentPosition = converter.convert(event.position)
@ -520,6 +529,7 @@ class AudioPlayerFragment : Fragment() {
sliderValue = event.position.toFloat()
}
private fun onPlaybackServiceChanged(event: FlowEvent.PlaybackServiceEvent) {
when (event.action) {
FlowEvent.PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN -> (activity as MainActivity).setPlayerVisible(false)
@ -527,6 +537,7 @@ class AudioPlayerFragment : Fragment() {
// PlaybackServiceEvent.Action.SERVICE_RESTARTED -> (activity as MainActivity).setPlayerVisible(true)
}
}
@UnstableApi
fun updateUi(media: Playable) {
Logd(TAG, "updateUi called $media")
@ -578,7 +589,8 @@ class AudioPlayerFragment : Fragment() {
withContext(Dispatchers.Main) {
Logd(TAG, "subscribe: ${currentMedia?.getEpisodeTitle()}")
displayMediaInfo(currentMedia!!)
// shownoteView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes?:"No notes", "text/html", "utf-8", "about:blank")
(activity as MainActivity).setPlayerVisible(curMedia != null)
// shownoteView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes?:"No notes", "text/html", "utf-8", "about:blank")
Logd(TAG, "Webview loaded")
}
}.invokeOnCompletion { throwable ->
@ -738,7 +750,10 @@ class AudioPlayerFragment : Fragment() {
private var loadItemsRunning = false
fun loadMediaInfo() {
Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}")
val actMain = (activity as MainActivity)
var i = 0
while (curMedia == null && i++ < 6) runBlocking { delay(500) }
if (curMedia == null) {
if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false)
return
@ -747,6 +762,7 @@ class AudioPlayerFragment : Fragment() {
loadItemsRunning = true
if (!actMain.isPlayerVisible()) actMain.setPlayerVisible(true)
val curMediaChanged = currentMedia == null || curMedia?.getIdentifier() != currentMedia?.getIdentifier()
if (curMedia?.getIdentifier() != currentMedia?.getIdentifier()) updateUi(curMedia!!)
if (!isCollapsed && curMediaChanged) {
updateDetails()
Logd(TAG, "loadMediaInfo loading details ${curMedia?.getIdentifier()}")
@ -761,7 +777,6 @@ class AudioPlayerFragment : Fragment() {
if (item != null) setItem(item)
setChapterDividers()
sleepTimerActive = isSleepTimerActive()
if (currentMedia != null) updateUi(currentMedia!!)
// TODO: disable for now
// if (!includingChapters) loadMediaInfo(true)
}.invokeOnCompletion { throwable ->

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.BaseEpisodesListFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.download.DownloadStatus
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeFilter
@ -10,7 +10,10 @@ import ac.mdiq.podcini.ui.actions.SwipeAction
import ac.mdiq.podcini.ui.actions.SwipeActions
import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.*
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.EpisodeLazyColumn
import ac.mdiq.podcini.ui.compose.EpisodeVM
import ac.mdiq.podcini.ui.compose.InforBar
import ac.mdiq.podcini.ui.utils.EmptyViewHandler
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
@ -42,7 +45,7 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene
protected var page: Int = 1
private var displayUpArrow = false
var _binding: BaseEpisodesListFragmentBinding? = null
var _binding: ComposeFragmentBinding? = null
protected val binding get() = _binding!!
protected var infoBarText = mutableStateOf("")
@ -59,7 +62,7 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = BaseEpisodesListFragmentBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar
@ -79,7 +82,7 @@ abstract class BaseEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListene
swipeActions = SwipeActions(this, TAG)
lifecycle.addObserver(swipeActions)
binding.lazyColumn.setContent {
binding.mainView.setContent {
CustomTheme(requireContext()) {
Column {
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()})

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.DownloadsFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
@ -20,7 +20,10 @@ import ac.mdiq.podcini.ui.actions.SwipeAction
import ac.mdiq.podcini.ui.actions.SwipeActions
import ac.mdiq.podcini.ui.actions.SwipeActions.NoActionSwipeAction
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.*
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.EpisodeLazyColumn
import ac.mdiq.podcini.ui.compose.EpisodeVM
import ac.mdiq.podcini.ui.compose.InforBar
import ac.mdiq.podcini.ui.dialog.EpisodeFilterDialog
import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog
import ac.mdiq.podcini.ui.dialog.SwitchQueueDialog
@ -37,7 +40,8 @@ import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.*
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
@ -56,7 +60,7 @@ import java.util.*
*/
@UnstableApi class DownloadsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: DownloadsFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private var runningDownloads: Set<String> = HashSet()
@ -74,7 +78,7 @@ import java.util.*
private var displayUpArrow = false
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = DownloadsFragmentBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar
@ -88,12 +92,11 @@ import java.util.*
// }
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
swipeActions = SwipeActions(this, TAG)
swipeActions.setFilter(EpisodeFilter(EpisodeFilter.States.downloaded.name))
binding.lazyColumn.setContent {
binding.mainView.setContent {
CustomTheme(requireContext()) {
Column {
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {swipeActions.showDialog()})

View File

@ -1,14 +1,10 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.databinding.EpisodeHomeFragmentBinding
import ac.mdiq.podcini.databinding.EpisodeInfoFragmentBinding
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient
import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected
import ac.mdiq.podcini.net.sync.SynchronizationSettings.wifiSyncEnabledKey
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.net.utils.NetworkUtils.fetchHtmlSource
import ac.mdiq.podcini.net.utils.NetworkUtils.isEpisodeHeadDownloadAllowed
import ac.mdiq.podcini.playback.base.InTheatre
@ -16,7 +12,6 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo
import ac.mdiq.podcini.preferences.UsageStatistics
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Queues.addToQueue
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.realm
@ -24,18 +19,12 @@ import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Rating
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.*
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.ChaptersDialog
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.LargeTextEditingDialog
import ac.mdiq.podcini.ui.compose.*
import ac.mdiq.podcini.ui.dialog.ShareDialog
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.utils.ThemeUtils
@ -109,7 +98,7 @@ import java.util.*
*/
@UnstableApi
class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: EpisodeInfoFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private var homeFragment: EpisodeHomeFragment? = null
@ -126,7 +115,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var hasMedia by mutableStateOf(true)
var rating by mutableStateOf(episode?.rating ?: Rating.UNRATED.code)
private var inQueue by mutableStateOf(if (episode != null) curQueue.contains(episode!!) else false)
var isPlayed by mutableStateOf(episode?.isPlayed() ?: false)
var isPlayed by mutableIntStateOf(episode?.playState ?: PlayState.UNSPECIFIED.code)
private var webviewData by mutableStateOf("")
@ -141,7 +130,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = EpisodeInfoFragmentBinding.inflate(inflater, container, false)
_binding = ComposeFragmentBinding.inflate(inflater, container, false)
Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar
@ -150,7 +139,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
toolbar.setOnMenuItemClickListener(this)
binding.composeView.setContent{
binding.mainView.setContent{
CustomTheme(requireContext()) {
MainView()
}
@ -188,6 +177,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
var showChaptersDialog by remember { mutableStateOf(false) }
if (showChaptersDialog && episode?.media != null) ChaptersDialog(media = episode!!.media!!, onDismissRequest = {showChaptersDialog = false})
var showPlayStateDialog by remember { mutableStateOf(false) }
if (showPlayStateDialog) PlayStateDialog(listOf(episode!!)) { showPlayStateDialog = false }
Column {
Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) {
val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null
@ -200,39 +192,40 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
Row(modifier = Modifier.padding(top = 4.dp), verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(0.4f))
val playedIconRes = if (!isPlayed) R.drawable.ic_mark_unplayed else R.drawable.ic_mark_played
val playedIconRes = PlayState.fromCode(isPlayed).res
Icon(imageVector = ImageVector.vectorResource(playedIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "isPlayed",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp)
.clickable(onClick = {
if (isPlayed) {
setPlayState(Episode.PlayState.UNPLAYED.code, false, episode!!)
if (isProviderConnected && episode?.feed?.isLocalFeed != true && episode?.media != null) {
val actionNew: EpisodeAction = EpisodeAction.Builder(episode!!, EpisodeAction.NEW).currentTimestamp().build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(requireContext(), actionNew)
}
} else {
setPlayState(Episode.PlayState.PLAYED.code, true, episode!!)
if (episode?.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) {
val media: EpisodeMedia? = episode?.media
// not all items have media, Gpodder only cares about those that do
if (isProviderConnected && media != null) {
val actionPlay: EpisodeAction = EpisodeAction.Builder(episode!!, EpisodeAction.PLAY)
.currentTimestamp()
.started(media.getDuration() / 1000)
.position(media.getDuration() / 1000)
.total(media.getDuration() / 1000)
.build()
SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(requireContext(), actionPlay)
}
}
}
showPlayStateDialog = true
// if (isPlayed) {
// setPlayState(PlayState.UNPLAYED.code, false, episode!!)
// if (isProviderConnected && episode?.feed?.isLocalFeed != true && episode?.media != null) {
// val actionNew: EpisodeAction = EpisodeAction.Builder(episode!!, EpisodeAction.NEW).currentTimestamp().build()
// SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(requireContext(), actionNew)
// }
// } else {
// setPlayState(PlayState.PLAYED.code, true, episode!!)
// if (episode?.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) {
// val media: EpisodeMedia? = episode?.media
// // not all items have media, Gpodder only cares about those that do
// if (isProviderConnected && media != null) {
// val actionPlay: EpisodeAction = EpisodeAction.Builder(episode!!, EpisodeAction.PLAY)
// .currentTimestamp()
// .started(media.getDuration() / 1000)
// .position(media.getDuration() / 1000)
// .total(media.getDuration() / 1000)
// .build()
// SynchronizationQueueSink.enqueueEpisodeActionIfSyncActive(requireContext(), actionPlay)
// }
// }
// }
}))
if (episode?.media != null) {
Spacer(modifier = Modifier.weight(0.2f))
val inQueueIconRes = if (inQueue) R.drawable.ic_playlist_play else R.drawable.ic_playlist_remove
Icon(imageVector = ImageVector.vectorResource(inQueueIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "inQueue",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = {
if (inQueue) removeFromQueue(episode!!) else addToQueue(true, episode!!)
if (inQueue) removeFromQueue(episode!!) else addToQueue(episode!!)
}))
}
Spacer(modifier = Modifier.weight(0.2f))
@ -625,7 +618,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (episode != null) {
rating = episode!!.rating
inQueue = curQueue.contains(episode!!)
isPlayed = episode!!.isPlayed()
isPlayed = episode!!.playState
}
onFragmentLoaded()
itemLoaded = true

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FeedItemListFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.download.DownloadStatus
import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.preferences.UserPreferences
@ -10,11 +10,8 @@ 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.database.RealmDB.upsertBlk
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.storage.model.*
import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor
import ac.mdiq.podcini.ui.actions.SwipeAction
import ac.mdiq.podcini.ui.actions.SwipeActions
@ -36,7 +33,10 @@ import android.view.*
import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -55,6 +55,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
@ -72,7 +73,7 @@ import java.util.concurrent.Semaphore
*/
@UnstableApi class FeedEpisodesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FeedItemListFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var swipeActions: SwipeActions
@ -87,6 +88,7 @@ import java.util.concurrent.Semaphore
private var headerCreated = false
private var feedID: Long = 0
private var feed by mutableStateOf<Feed?>(null)
var rating by mutableStateOf(Rating.UNRATED.code)
private val episodes = mutableStateListOf<Episode>()
private val vms = mutableStateListOf<EpisodeVM>()
@ -112,7 +114,7 @@ import java.util.concurrent.Semaphore
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Logd(TAG, "fragment onCreateView")
_binding = FeedItemListFragmentBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
binding.toolbar.inflateMenu(R.menu.feed_episodes)
binding.toolbar.setOnMenuItemClickListener(this)
@ -169,7 +171,7 @@ import java.util.concurrent.Semaphore
requireActivity().supportFragmentManager.executePendingTransactions()
}
Column {
FeedEpisodesHeader(activity = (activity as MainActivity), feed = feed, filterButColor = filterButColor.value, filterClickCB = {filterClick()}, filterLongClickCB = {filterLongClick()})
FeedEpisodesHeader(activity = (activity as MainActivity), filterButColor = filterButColor.value, filterClickCB = {filterClick()}, filterLongClickCB = {filterLongClick()})
InforBar(infoBarText, leftAction = leftActionState, rightAction = rightActionState, actionConfig = {
swipeActions.showDialog()
})
@ -225,8 +227,14 @@ import java.util.concurrent.Semaphore
@kotlin.OptIn(ExperimentalFoundationApi::class)
@Composable
fun FeedEpisodesHeader(activity: MainActivity, feed: Feed?, filterButColor: Color, filterClickCB: ()->Unit, filterLongClickCB: ()->Unit) {
fun FeedEpisodesHeader(activity: MainActivity, filterButColor: Color, filterClickCB: ()->Unit, filterLongClickCB: ()->Unit) {
val textColor = MaterialTheme.colorScheme.onSurface
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(listOf(feed!!)) {
showChooseRatingDialog = false
feed = realm.query(Feed::class).query("id == $0", feed!!.id).first().find()!!
rating = feed!!.rating
}
ConstraintLayout(modifier = Modifier.fillMaxWidth().height(120.dp)) {
val (bgImage, bgColor, controlRow, image1, image2, imgvCover, taColumn) = createRefs()
AsyncImage(model = feed?.imageUrl?:"", contentDescription = "bgImage", contentScale = ContentScale.FillBounds,
@ -238,7 +246,7 @@ import java.util.concurrent.Semaphore
start.linkTo(parent.start)
end.linkTo(parent.end)
})
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface.copy(alpha = 0.7f))
Box(modifier = Modifier.fillMaxSize().background(MaterialTheme.colorScheme.surface.copy(alpha = 0.75f))
.constrainAs(bgColor) {
bottom.linkTo(parent.bottom)
top.linkTo(parent.top)
@ -249,20 +257,30 @@ import java.util.concurrent.Semaphore
.constrainAs(controlRow) {
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
width = Dimension.fillToConstraints
}, verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(0.7f))
val ratingIconRes = Rating.fromCode(rating).res
Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(30.dp).height(30.dp).clickable(onClick = {
showChooseRatingDialog = true
}))
Spacer(modifier = Modifier.weight(0.2f))
Icon(imageVector = ImageVector.vectorResource(R.drawable.arrows_sort), tint = textColor, contentDescription = "butSort",
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).clickable(onClick = { SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog") }))
Spacer(modifier = Modifier.width(15.dp))
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_filter_white), tint = if (filterButColor == Color.White) textColor else filterButColor, contentDescription = "butFilter",
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).combinedClickable(onClick = filterClickCB, onLongClick = filterLongClickCB))
Spacer(modifier = Modifier.width(15.dp))
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_settings_white), tint = textColor, contentDescription = "butShowSettings",
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).clickable(onClick = {
if (feed != null) {
val fragment = FeedSettingsFragment.newInstance(feed)
val fragment = FeedSettingsFragment.newInstance(feed!!)
activity.loadChildFragment(fragment, TransitionEffect.SLIDE)
}
}))
Spacer(modifier = Modifier.weight(0.5f))
Text(episodes.size.toString() + " / " + feed?.episodes?.size?.toString(), textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.bodyLarge)
Spacer(modifier = Modifier.weight(0.4f))
Text(episodes.size.toString() + " / " + feed?.episodes?.size?.toString(), textAlign = TextAlign.End, color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge)
}
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
@ -274,21 +292,23 @@ import java.util.concurrent.Semaphore
// bottom.linkTo(parent.bottom)
// end.linkTo(parent.end)
// })
AsyncImage(model = feed?.imageUrl?:"", contentDescription = "imgvCover", error = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(120.dp).height(120.dp).padding(start = 16.dp, end = 16.dp, bottom = 12.dp).constrainAs(imgvCover) {
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
}.clickable(onClick = {
if (feed != null) {
val fragment = FeedInfoFragment.newInstance(feed)
activity.loadChildFragment(fragment, TransitionEffect.SLIDE)
}
}))
Column(Modifier.fillMaxWidth().constrainAs(taColumn) {
top.linkTo(imgvCover.top)
start.linkTo(imgvCover.end) }) {
Text(feed?.title?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(feed?.author?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
Row(verticalAlignment = Alignment.Top, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).constrainAs(imgvCover) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
}) {
AsyncImage(model = feed?.imageUrl ?: "", contentDescription = "imgvCover", error = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(100.dp).height(100.dp).padding(start = 16.dp, end = 16.dp).clickable(onClick = {
if (feed != null) {
val fragment = FeedInfoFragment.newInstance(feed!!)
activity.loadChildFragment(fragment, TransitionEffect.SLIDE)
}
}))
Column(Modifier.padding(top = 10.dp)) {
Text(feed?.title ?: "", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(feed?.author ?: "", color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
}
@ -386,7 +406,7 @@ import java.util.concurrent.Semaphore
} catch (e: InterruptedException) { throw RuntimeException(e) }
}.start()
}
R.id.sort_items -> SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog")
// R.id.sort_items -> SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog")
// R.id.filter_items -> {}
// R.id.settings -> {
// if (feed != null) {
@ -651,6 +671,7 @@ import java.util.concurrent.Semaphore
}
withContext(Dispatchers.Main) {
Logd(TAG, "loadItems subscribe called ${feed?.title}")
rating = feed?.rating ?: Rating.UNRATED.code
swipeActions.setFilter(feed?.episodeFilter)
refreshHeaderView()
// if (feed != null) {

View File

@ -1,8 +1,8 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.databinding.EditTextDialogBinding
import ac.mdiq.podcini.databinding.FeedinfoBinding
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
import ac.mdiq.podcini.net.utils.HtmlToPlainText
@ -42,8 +42,11 @@ import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
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.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -62,6 +65,7 @@ import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
import androidx.documentfile.provider.DocumentFile
import androidx.fragment.app.Fragment
import androidx.fragment.compose.AndroidFragment
@ -80,7 +84,7 @@ import java.util.concurrent.ExecutionException
@UnstableApi
class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FeedinfoBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var feed: Feed
@ -96,7 +100,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FeedinfoBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar
toolbar.title = ""
@ -108,7 +112,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
txtvAuthor = feed.author ?: ""
txtvUrl = feed.downloadUrl
binding.mainUI.setContent {
binding.mainView.setContent {
CustomTheme(requireContext()) {
if (showRemoveFeedDialog) RemoveFeedDialog(listOf(feed), onDismissRequest = {showRemoveFeedDialog = false}) {
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
@ -200,21 +204,23 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// bottom.linkTo(parent.bottom)
// end.linkTo(parent.end)
// })
AsyncImage(model = feed.imageUrl?:"", contentDescription = "imgvCover", error = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(120.dp).height(120.dp).padding(start = 16.dp, end = 16.dp, bottom = 12.dp).constrainAs(imgvCover) {
bottom.linkTo(parent.bottom)
start.linkTo(parent.start)
}.clickable(onClick = {
Row(verticalAlignment = Alignment.Top, modifier = Modifier.fillMaxWidth().padding(bottom = 12.dp).constrainAs(imgvCover) {
top.linkTo(parent.top)
start.linkTo(parent.start)
end.linkTo(parent.end)
width = Dimension.fillToConstraints
}) {
AsyncImage(model = feed.imageUrl ?: "", contentDescription = "imgvCover", error = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(100.dp).height(100.dp).padding(start = 16.dp, end = 16.dp).clickable(onClick = {
// if (feed != null) {
// val fragment = FeedInfoFragment.newInstance(feed)
// (activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
// }
}))
Column(Modifier.constrainAs(taColumn) {
top.linkTo(imgvCover.top)
start.linkTo(imgvCover.end) }) {
Text(feed.title ?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(text = txtvAuthor, color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
}))
Column(Modifier.padding(top = 10.dp)) {
Text(feed.title ?: "", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(text = txtvAuthor, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
}

View File

@ -1,9 +1,8 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.LogsFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
import ac.mdiq.podcini.storage.database.Feeds.getFeed
import ac.mdiq.podcini.storage.database.Feeds.getFeedByTitleAndAuthor
import ac.mdiq.podcini.storage.database.RealmDB.realm
@ -14,8 +13,8 @@ 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
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.ConfirmAddYoutubeEpisode
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
@ -51,7 +50,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
@ -66,23 +64,26 @@ import kotlinx.coroutines.withContext
import java.util.*
class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: LogsFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private val shareLogs = mutableStateListOf<ShareLog>()
private val subscriptionLogs = mutableStateListOf<SubscriptionLog>()
private val downloadLogs = mutableStateListOf<DownloadResult>()
// private var showShared by mutableStateOf(true)
// private var showSubscription by mutableStateOf(false)
private var displayUpArrow = false
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Logd(TAG, "fragment onCreateView")
_binding = LogsFragmentBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
binding.toolbar.inflateMenu(R.menu.logs)
binding.toolbar.setOnMenuItemClickListener(this)
binding.lazyColumn.setContent {
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
binding.mainView.setContent {
CustomTheme(requireContext()) {
when {
downloadLogs.isNotEmpty() -> DownloadLogView()
@ -95,6 +96,11 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_UP_ARROW, displayUpArrow)
super.onSaveInstanceState(outState)
}
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
@ -489,5 +495,6 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
companion object {
val TAG: String = LogsFragment::class.simpleName ?: "Anonymous"
private const val KEY_UP_ARROW = "up_arrow"
}
}

View File

@ -1,8 +1,6 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.NavListBinding
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.database.Feeds.getFeedCount
@ -42,6 +40,7 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
@ -57,30 +56,30 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.math.max
class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
val TAG = this::class.simpleName ?: "Anonymous"
private var _binding: NavListBinding? = null
private val binding get() = _binding!!
private val feeds = mutableStateListOf<Feed>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = NavListBinding.inflate(inflater)
checkHiddenItems()
getRecentPodcasts()
binding.mainView.setContent {
CustomTheme(requireContext()) {
MainView()
val composeView = ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) {
MainView()
}
}
}
Logd(TAG, "fragment onCreateView")
setupDrawerRoundBackground(binding.root)
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view: View, insets: WindowInsetsCompat ->
setupDrawerRoundBackground(composeView)
ViewCompat.setOnApplyWindowInsetsListener(composeView) { view: View, insets: WindowInsetsCompat ->
val bars: Insets = insets.getInsets(WindowInsetsCompat.Type.systemBars())
view.setPadding(bars.left, bars.top, bars.right, 0)
var navigationBarHeight = 0f
@ -94,7 +93,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
insets
}
prefs!!.registerOnSharedPreferenceChangeListener(this)
return binding.root
return composeView
}
private fun checkHiddenItems() {
@ -171,7 +170,6 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
prefs!!.unregisterOnSharedPreferenceChangeListener(this)
super.onDestroyView()
}
@ -239,7 +237,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
}
fun getLastNavFragment(): String {
val lastFragment: String = prefs!!.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionsFragment.TAG)?:""
val lastFragment: String = prefs?.getString(PREF_LAST_FRAGMENT_TAG, SubscriptionsFragment.TAG)?:""
return lastFragment
}

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.OnlineFeedviewFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.net.feed.FeedBuilder
import ac.mdiq.podcini.net.feed.FeedUrlNotFoundException
@ -81,7 +81,7 @@ import kotlin.concurrent.Volatile
*/
@OptIn(UnstableApi::class)
class OnlineFeedFragment : Fragment() {
private var _binding: OnlineFeedviewFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private var displayUpArrow = false
@ -124,7 +124,7 @@ class OnlineFeedFragment : Fragment() {
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Logd(TAG, "fragment onCreateView")
_binding = OnlineFeedviewFragmentBinding.inflate(layoutInflater)
_binding = ComposeFragmentBinding.inflate(layoutInflater)
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)

View File

@ -2,7 +2,7 @@ package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.CheckboxDoNotShowAgainBinding
import ac.mdiq.podcini.databinding.QueueFragmentBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.download.DownloadStatus
import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
@ -85,7 +85,7 @@ import kotlin.math.max
@UnstableApi class QueuesFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: QueueFragmentBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var emptyViewHandler: EmptyViewHandler
@ -126,7 +126,7 @@ import kotlin.math.max
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
_binding = QueueFragmentBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
toolbar = binding.toolbar

View File

@ -2,7 +2,10 @@ package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.*
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.databinding.QuickFeedDiscoveryBinding
import ac.mdiq.podcini.databinding.QuickFeedDiscoveryItemBinding
import ac.mdiq.podcini.databinding.SelectCountryDialogBinding
import ac.mdiq.podcini.net.feed.discovery.ItunesTopListLoader
import ac.mdiq.podcini.net.feed.discovery.ItunesTopListLoader.Companion.prefs
import ac.mdiq.podcini.net.feed.discovery.PodcastSearchResult
@ -10,9 +13,9 @@ import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.OnlineFeedItem
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd
import android.content.DialogInterface
import android.os.Bundle
import android.util.DisplayMetrics
@ -24,7 +27,6 @@ import android.view.ViewGroup
import android.widget.*
import androidx.annotation.OptIn
import androidx.appcompat.widget.Toolbar
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
@ -37,7 +39,6 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.compose.ConstraintLayout
@ -255,7 +256,7 @@ class QuickDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
* Searches iTunes store for top podcasts and displays results in a list.
*/
class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FragmentSearchResultsBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var toolbar: MaterialToolbar
@ -295,7 +296,7 @@ class QuickDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment
_binding = FragmentSearchResultsBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
binding.mainView.setContent {
CustomTheme(requireContext()) {

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FragmentSearchResultsBinding
import ac.mdiq.podcini.databinding.ComposeFragmentBinding
import ac.mdiq.podcini.net.feed.discovery.PodcastSearchResult
import ac.mdiq.podcini.net.feed.discovery.PodcastSearcher
import ac.mdiq.podcini.net.feed.discovery.PodcastSearcherRegistry
@ -48,7 +48,7 @@ import kotlinx.coroutines.withContext
class SearchResultsFragment : Fragment() {
private var _binding: FragmentSearchResultsBinding? = null
private var _binding: ComposeFragmentBinding? = null
private val binding get() = _binding!!
private var searchProvider: PodcastSearcher? = null
@ -74,7 +74,7 @@ class SearchResultsFragment : Fragment() {
}
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentSearchResultsBinding.inflate(inflater)
_binding = ComposeFragmentBinding.inflate(inflater)
Logd(TAG, "fragment onCreateView")
binding.mainView.setContent {
CustomTheme(requireContext()) {

View File

@ -381,7 +381,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val dir = 1 - 2*feedOrderDir // get from 0, 1 to 1, -1
val comparator: Comparator<Feed> = when (feedOrder) {
FeedSortOrder.UNPLAYED_NEW_OLD.index -> {
val queryString = "feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code})"
val queryString = "feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code})"
val counterMap: MutableMap<Long, Long> = mutableMapOf()
for (f in feedList_) {
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
@ -403,7 +403,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
FeedSortOrder.MOST_PLAYED.index -> {
val queryString = "feedId == $0 AND playState == ${Episode.PlayState.PLAYED.code}"
val queryString = "feedId == $0 AND playState == ${PlayState.PLAYED.code}"
val counterMap: MutableMap<Long, Long> = mutableMapOf()
for (f in feedList_) {
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
@ -436,7 +436,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
FeedSortOrder.LAST_UPDATED_UNPLAYED_NEW_OLD.index -> {
val queryString =
"feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code}) SORT(pubDate DESC)"
"feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) SORT(pubDate DESC)"
val counterMap: MutableMap<Long, Long> = mutableMapOf()
for (f in feedList_) {
val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L
@ -458,7 +458,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
FeedSortOrder.MOST_DOWNLOADED_UNPLAYED.index -> {
val queryString =
"feedId == $0 AND (playState == ${Episode.PlayState.NEW.code} OR playState == ${Episode.PlayState.UNPLAYED.code}) AND media.downloaded == true"
"feedId == $0 AND (playState == ${PlayState.NEW.code} OR playState == ${PlayState.UNPLAYED.code}) AND media.downloaded == true"
val counterMap: MutableMap<Long, Long> = mutableMapOf()
for (f in feedList_) {
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
@ -469,7 +469,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
// doing FEED_ORDER_NEW
else -> {
val queryString = "feedId == $0 AND playState == ${Episode.PlayState.NEW.code}"
val queryString = "feedId == $0 AND playState == ${PlayState.NEW.code}"
val counterMap: MutableMap<Long, Long> = mutableMapOf()
for (f in feedList_) {
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
@ -954,13 +954,13 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
})
)
if (feed.rating != Rating.UNRATED.code)
Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary,
contentDescription = "rating",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).constrainAs(rating) {
start.linkTo(parent.start)
centerVerticallyTo(coverImage)
})
// if (feed.rating != Rating.UNRATED.code)
// Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary,
// contentDescription = "rating",
// modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).constrainAs(rating) {
// start.linkTo(parent.start)
// centerVerticallyTo(coverImage)
// })
}
val textColor = MaterialTheme.colorScheme.onSurface
Column(Modifier.weight(1f).padding(start = 10.dp).combinedClickable(onClick = {
@ -984,8 +984,13 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
Logd(TAG, "long clicked: ${feed.title}")
})) {
Text(feed.title ?: "No title", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold))
Row {
if (feed.rating != Rating.UNRATED.code)
Icon(imageVector = ImageVector.vectorResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer))
Text(feed.title ?: "No title", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis,
style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold))
}
Text(feed.author ?: "No author", color = textColor, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium)
Row(Modifier.padding(top = 5.dp)) {
val measureString = remember { NumberFormat.getInstance().format(feed.episodes.size.toLong()) + " : " +

View File

@ -447,7 +447,7 @@ class StatisticsFragment : Fragment() {
else {
// progress import does not include playedDuration
if (includeMarkedAsPlayed) {
if (m.playbackCompletionTime > 0 || m.episodeOrFetch()?.playState == Episode.PlayState.PLAYED.code)
if (m.playbackCompletionTime > 0 || m.episodeOrFetch()?.playState == PlayState.PLAYED.code)
dur += m.duration
else if (m.position > 0) dur += m.position
} else dur += m.position

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z"/>
</vector>

View File

@ -0,0 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20.13,5.41l-1.41,-1.41l-9.19,9.19l-4.25,-4.24l-1.41,1.41l5.66,5.66z"/>
<path android:fillColor="@android:color/white" android:pathData="M5,18h14v2h-14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,4H4C2.89,4 2.01,4.89 2.01,6L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2V6C22,4.89 21.11,4 20,4zM8.5,15H7.3l-2.55,-3.5V15H3.5V9h1.25l2.5,3.5V9H8.5V15zM13.5,10.26H11v1.12h2.5v1.26H11v1.11h2.5V15h-4V9h4V10.26zM20.5,14c0,0.55 -0.45,1 -1,1h-4c-0.55,0 -1,-0.45 -1,-1V9h1.25v4.51h1.13V9.99h1.25v3.51h1.12V9h1.25V14z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M20,12c0,-1.1 0.9,-2 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2v4c1.1,0 1.99,0.9 1.99,2s-0.89,2 -2,2v4c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2v-4c-1.1,0 -2,-0.9 -2,-2zM15.58,16.8L12,14.5l-3.58,2.3 1.08,-4.12 -3.29,-2.69 4.24,-0.25L12,5.8l1.54,3.95 4.24,0.25 -3.29,2.69 1.09,4.11z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M21,12l-4.37,6.16C16.26,18.68 15.65,19 15,19h-3l0,-6H9v-3H3V7c0,-1.1 0.9,-2 2,-2h10c0.65,0 1.26,0.31 1.63,0.84L21,12zM10,15H7v-3H5v3H2v2h3v3h2v-3h3V15z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M10,16.5l6,-4.5 -6,-4.5v9zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M8,5.19L8,5l11,7 -2.55,1.63L8,5.19zM20,19.73l-5.11,-5.11L8,7.73 4.27,4 3,5.27l5,5L8,19l5.33,-3.4 5.4,5.4L20,19.73z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.5,2 2,6.5 2,12s4.5,10 10,10s10,-4.5 10,-10S17.5,2 12,2zM16.2,16.2L11,13V7h1.5v5.2l4.5,2.7L16.2,16.2z"/>
</vector>

View File

@ -4,26 +4,18 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:orientation="vertical"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/add_feed_label"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/add_feed_label"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<androidx.cardview.widget.CardView
android:layout_width="match_parent"

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/episodes_list_fragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<!-- <androidx.compose.ui.platform.ComposeView-->
<!-- android:id="@+id/infobar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"/>-->
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/lazyColumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,26 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/episode_info_fragment"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:orientation="vertical">
android:id="@+id/compose_fragment">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
android:layout_height="?android:attr/actionBarSize"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView"
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<!-- <androidx.compose.ui.platform.ComposeView-->
<!-- android:id="@+id/infobar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"/>-->
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/lazyColumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
app:navigationIcon="?homeAsUpIndicator"
app:navigationContentDescription="@string/toolbar_back_button_content_description"/>
<!-- <androidx.compose.ui.platform.ComposeView-->
<!-- android:id="@+id/header"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"/>-->
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/feedinfo_fragment"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_collapseMode="pin"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainUI"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/fragment_search_results">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/discover"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,22 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/logs_label" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/lazyColumn"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/nav_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/online_feed"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -1,36 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/queue_fragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator"/>
<!-- <androidx.compose.ui.platform.ComposeView-->
<!-- android:id="@+id/infobar"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"/>-->
</com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/mainView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View File

@ -5,26 +5,18 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:id="@+id/search_fragment">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
android:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/search_label"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
</com.google.android.material.appbar.AppBarLayout>
android:minHeight="?android:attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:title="@string/search_label"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<com.google.android.material.chip.Chip
android:id="@+id/feed_title_chip"

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<!-- the order is opposite of the typical menu:
catered to FAB speed dial, which somehow shows the item in reverse.
E.g., item @id/delete_batch is the first in the xml,
visually it will be shown at the bottom of the list of actions.
-->
<item
android:id="@+id/delete_batch"
android:icon="@drawable/ic_delete"
android:title="@string/delete_episode_label" />
<item
android:id="@+id/download_batch"
android:icon="@drawable/ic_download"
android:title="@string/download_label" />
<item
android:id="@+id/toggle_played_batch"
android:icon="@drawable/ic_mark_played"
android:title="@string/toggle_played_label" />
<item
android:id="@+id/remove_from_queue_batch"
android:icon="@drawable/ic_playlist_remove"
android:title="@string/remove_from_queue_label" />
<item
android:id="@+id/add_to_queue_batch"
android:icon="@drawable/ic_playlist_play"
android:title="@string/add_to_queue_label" />
<item
android:id="@+id/put_in_queue_batch"
android:icon="@drawable/ic_playlist_play"
android:title="@string/put_in_queue_label" />
<item
android:id="@+id/toggle_favorite_batch"
android:icon="@drawable/ic_star"
android:title="@string/toggle_favorite_label" />
</menu>

View File

@ -8,12 +8,12 @@
android:icon="@drawable/playlist_play"
custom:showAsAction="always"/>
<item
android:id="@+id/sort_items"
android:icon="@drawable/arrows_sort"
android:title="@string/sort"
custom:showAsAction="always">
</item>
<!-- <item-->
<!-- android:id="@+id/sort_items"-->
<!-- android:icon="@drawable/arrows_sort"-->
<!-- android:title="@string/sort"-->
<!-- custom:showAsAction="always">-->
<!-- </item>-->
<!-- <item-->
<!-- android:id="@+id/filter_items"-->

View File

@ -1,59 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@id/skip_episode_item"
android:menuCategory="container"
android:title="@string/skip_episode_label" />
<item
android:id="@+id/mark_read_item"
android:menuCategory="container"
android:title="@string/mark_read_label" />
<item
android:id="@+id/mark_unread_item"
android:menuCategory="container"
android:title="@string/mark_unread_label" />
<item
android:id="@+id/add_to_queue_item"
android:menuCategory="container"
android:title="@string/add_to_queue_label" />
<item
android:id="@+id/remove_from_queue_item"
android:menuCategory="container"
android:title="@string/remove_from_queue_label" />
<item
android:id="@+id/remove_item"
android:menuCategory="container"
android:title="@string/delete_label" />
<!-- <item-->
<!-- android:id="@+id/add_to_favorites_item"-->
<!-- android:menuCategory="container"-->
<!-- android:title="@string/add_to_favorite_label" />-->
<!-- <item-->
<!-- android:id="@+id/remove_from_favorites_item"-->
<!-- android:menuCategory="container"-->
<!-- android:title="@string/remove_from_favorite_label" />-->
<item
android:id="@+id/reset_position"
android:menuCategory="container"
android:title="@string/reset_position" />
<item
android:id="@+id/share_item"
android:icon="@drawable/ic_share"
android:menuCategory="container"
android:title="@string/share_label" />
<item
android:id="@+id/multi_select"
android:menuCategory="container"
android:title="@string/multi_select"
android:visible="false" />
</menu>

View File

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/select_all_above"
android:title="@string/select_all_above">
</item>
<item
android:id="@+id/select_all_below"
android:title="@string/select_all_below">
</item>
</menu>

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/select_all_above"
android:title="@string/select_all_above"
android:icon="@drawable/baseline_arrow_upward_24"
app:showAsAction="always"/>
<item
android:id="@+id/select_all_below"
android:title="@string/select_all_below"
android:icon="@drawable/baseline_arrow_downward_24"
app:showAsAction="always"/>
<item
android:id="@+id/select_toggle"
android:title="@string/select_all_label"
app:showAsAction="always"/>
</menu>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/move_to_top_item"
android:menuCategory="container"
android:title="@string/move_to_top_label" />
<item
android:id="@+id/move_to_bottom_item"
android:menuCategory="container"
android:title="@string/move_to_bottom_label" />
<!-- rest of the menu items can be found in the generic feeditemlist_context.xml -->
</menu>

View File

@ -228,31 +228,14 @@
<item quantity="other">تم مسح %d حلقات منزلة.</item>
</plurals>
<string name="removed_inbox_label">تم الإزالة من صندوق الوارد</string>
<string name="mark_read_label">علمها كمشغلة</string>
<string name="toggle_played_label">فعل حالة التشغيل</string>
<string name="marked_as_played_label">تم الاستماع</string>
<string name="marked_as_unplayed_label">لم يتم الاستماع بعد</string>
<string name="mark_read_no_media_label">علمها كمقروءة</string>
<string name="play_this_to_seek_position">للإنتقال للتوقيتات, يجب أن تشغل الحلقة</string>
<plurals name="marked_read_batch_label">
<item quantity="zero">%d حلقة علمت كـ مقروءة.</item>
<item quantity="one">%d حلقة علمت كـ مقروءة.</item>
<item quantity="two">%d حلقتان علمتا كـ مقروءة.</item>
<item quantity="few">%d حلقات علمت كـ مقروءة.</item>
<item quantity="many">%d حلقات علمت كـ مقروءة. </item>
<item quantity="other">%d حلقات علمت كمقروءة.</item>
</plurals>
<string name="mark_unread_label">علمها كغير مشغلة</string>
<string name="mark_unread_label_no_media">علمها كغير مقروءة</string>
<plurals name="marked_unread_batch_label">
<item quantity="zero">%d حلقة علمت بأنه لم يتم تشغيلها.</item>
<item quantity="one">%d حلقة علمت بأنه لم يتم تشغيلها.</item>
<item quantity="two"> %d حلقتان علمتا بأنه لم يتم تشغيلها.</item>
<item quantity="few">%d حلقات علمت بأنه لم يتم تشغيلها.</item>
<item quantity="many">%d حلقة علمت بأنه لم يتم تشغيلها.</item>
<item quantity="other">%d حلقات علمت بأنه لم يتم تشغيلها.</item>
</plurals>
<string name="add_to_queue_label">اضف للائحة الاستماع</string>
<plurals name="added_to_queue_batch_label">
<item quantity="zero">%d حلقة أضيفت إلى لائحة الاستماع.</item>

View File

@ -104,9 +104,9 @@
<string name="stream_label">Sentir</string>
<string name="delete_label">Desaniciar</string>
<string name="delete_failed">Nun ye posible desaniciar el ficheru. Reaniciar el preséu podría ayudar.</string>
<string name="mark_read_label">Marcar como «Reprodúxose»</string>
<string name="mark_unread_label">Marcar como «Ensin reproducir»</string>
<string name="mark_unread_label_no_media">Marcar como «Ensin lleer»</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">Amestóse %d episodiu a la cola.</item>
<item quantity="other">Amestáronse %d episodios a la cola.</item>

View File

@ -193,29 +193,15 @@
<item quantity="other">Dilamet ez eus bet %d rann pellgarget.</item>
</plurals>
<string name="removed_inbox_label">Tennet eo bet eus ar voest degemer</string>
<string name="mark_read_label">Merkañ evel lennet</string>
<string name="toggle_played_label">Lakaat da gemm stad al lenn</string>
<string name="marked_as_played_label">Lakaet war-well evel lennet</string>
<string name="marked_as_unplayed_label">Lakaet war-well evel lakaet da baouez</string>
<string name="mark_read_no_media_label">Merkañ evel lennet</string>
<string name="play_this_to_seek_position">Evit kemmañ al lec\'hiadur e rankit lenn ar rann</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d rann merket evel lennet</item>
<item quantity="two">%d rann merket evel lennet</item>
<item quantity="few">%d rann merket evel lennet</item>
<item quantity="many">%d rann merket evel lennet</item>
<item quantity="other">%d rann merket evel lennet</item>
</plurals>
<string name="mark_unread_label">Merkañ evel anlennet</string>
<string name="mark_unread_label_no_media">Merkañ evel anlennet</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d rann merket evel anlennet. </item>
<item quantity="two">%d rann merket evel anlennet. </item>
<item quantity="few">%d rann merket evel anlennet. </item>
<item quantity="many">%d rann merket evel anlennet.</item>
<item quantity="other">%d rann merket evel anlennet. </item>
</plurals>
<string name="add_to_queue_label">Ouzhpennañ el lost</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d rann ouzhpennet el lost</item>

View File

@ -188,23 +188,14 @@
<item quantity="other">%d episodis baixats, suprimits.</item>
</plurals>
<string name="removed_inbox_label">Eliminat de la Safata d\'entrada</string>
<string name="mark_read_label">Marca com a reproduït</string>
<string name="toggle_played_label">Commuta l\'estat de reproducció</string>
<string name="marked_as_played_label">Marca com a reproduït</string>
<string name="marked_as_unplayed_label">Marca com a no reproduït</string>
<string name="mark_read_no_media_label">Marcar com a llegit</string>
<string name="play_this_to_seek_position">Per a botar a posicions deus reproduir l\'episodi.</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episodi marcat com a reproduït.</item>
<item quantity="other">%d episodis marcats com a reproduïts. </item>
</plurals>
<string name="mark_unread_label">Marca com a pendent</string>
<string name="mark_unread_label_no_media">Marcar com a no llegit</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episodi marcat com a no llegit.</item>
<item quantity="other">%d episodis marcats com a no llegits.</item>
</plurals>
<string name="add_to_queue_label">Afegeix a la cua</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episodi afegit a la cua.</item>

View File

@ -217,27 +217,15 @@
<item quantity="other">%d stažených epizod smazáno.</item>
</plurals>
<string name="removed_inbox_label">Odebráno z nových</string>
<string name="mark_read_label">Označit jako poslechnuté</string>
<string name="toggle_played_label">Přepnutí stavu přehrávání</string>
<string name="marked_as_played_label">Označeno jako poslechnuté</string>
<string name="marked_as_unplayed_label">Označeno jako neposlechnuté</string>
<string name="mark_read_no_media_label">Označit jako poslechnuté</string>
<string name="play_this_to_seek_position">Pro přeskočení na pozice musíte epizodu přehrát</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d epizoda označena jako přehraná</item>
<item quantity="few">%d epizody označeny jako přehrané</item>
<item quantity="many">%d epizod označeno jako přehrané</item>
<item quantity="other">%d epizod označeno jako přehrané</item>
</plurals>
<string name="mark_unread_label">Označit jako neposlechnuté</string>
<string name="mark_unread_label_no_media">Označit jako nepřečtené</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d epizoda označena jako neposlechnutá</item>
<item quantity="few">%d epizody označeny jako neposlechnuté</item>
<item quantity="many">%d epizod označeno jako neposlechnuté</item>
<item quantity="other">%d epizod označeno jako neposlechnuté</item>
</plurals>
<string name="add_to_queue_label">Přidat do fronty</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d epizoda přidána do fronty</item>

View File

@ -199,23 +199,14 @@
<item quantity="other">%d overførte afsnit slettet.</item>
</plurals>
<string name="removed_inbox_label">Fjernet fra indbakken</string>
<string name="mark_read_label">Markér som afspillet</string>
<string name="toggle_played_label">Skift afspilningstilstand</string>
<string name="marked_as_played_label">Markér som afspillet</string>
<string name="marked_as_unplayed_label">Markér som uafspillet</string>
<string name="mark_read_no_media_label">Marker som læst</string>
<string name="play_this_to_seek_position">For at springe til positioner, er du nødt til at afspille afsnittet</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d afsnit markeret som afspillet.</item>
<item quantity="other">%d afsnit markeret som afspillet.</item>
</plurals>
<string name="mark_unread_label">Markér som uafspillet</string>
<string name="mark_unread_label_no_media">Marker som ulæst</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d afsnit markeret som uafspillet.</item>
<item quantity="other">%d afsnit markeret som uafspillede.</item>
</plurals>
<string name="add_to_queue_label">Føj til kø</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d afsnit føjet til køen.</item>

View File

@ -203,23 +203,14 @@
<item quantity="other">%d heruntergeladene Episoden gelöscht.</item>
</plurals>
<string name="removed_inbox_label">Aus dem Posteingang entfernt</string>
<string name="mark_read_label">Als gespielt markieren</string>
<string name="toggle_played_label">Abgespielt-Zustand umschalten</string>
<string name="marked_as_played_label">Als gespielt markieren</string>
<string name="marked_as_unplayed_label">Als ungespielt markieren</string>
<string name="mark_read_no_media_label">Als gelesen markieren</string>
<string name="play_this_to_seek_position">Um auf eine Position zu springen, musst du die Episode abspielen</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d Episode als gespielt markiert.</item>
<item quantity="other">%d Episoden als gespielt markiert.</item>
</plurals>
<string name="mark_unread_label">Als ungespielt markieren</string>
<string name="mark_unread_label_no_media">Als ungelesen markieren</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d Episode als ungespielt markiert.</item>
<item quantity="other">%d Episoden als ungespielt markiert.</item>
</plurals>
<string name="add_to_queue_label">Zur Warteschlange hinzufügen</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d Episode zur Warteschlange hinzugefügt.</item>

View File

@ -211,25 +211,14 @@
<item quantity="other">Eliminados %d episodios descargados.</item>
</plurals>
<string name="removed_inbox_label">Borrado de la bandeja de entrada</string>
<string name="mark_read_label">Marcar como reproducido</string>
<string name="toggle_played_label">Cambiar estado de reproducción</string>
<string name="marked_as_played_label">Marcar como reproducido</string>
<string name="marked_as_unplayed_label">Marcar como no reproducido</string>
<string name="mark_read_no_media_label">Marcar como leído</string>
<string name="play_this_to_seek_position">Para saltar a posiciones, necesitas reproducir el episodio</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%depisodio marcado como reproducido.</item>
<item quantity="many">%depisodios marcados como reproducidos.</item>
<item quantity="other">%depisodios marcados como reproducidos.</item>
</plurals>
<string name="mark_unread_label">Marcar como no reproducido</string>
<string name="mark_unread_label_no_media">Marcar como no leído</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episodio marcado como no reproducido.</item>
<item quantity="many">%d episodios marcados como no reproducidos.</item>
<item quantity="other">%d episodios marcados como no reproducidos.</item>
</plurals>
<string name="add_to_queue_label">Añadir a la cola</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episodio añadido a la cola.</item>

View File

@ -160,21 +160,11 @@
<item quantity="one">1 allalaaditud saade kustutatud.</item>
<item quantity="other">%d allalaaditud saadet kustutatud.</item>
</plurals>
<string name="mark_read_label">Märgi kuulatuks</string>
<string name="marked_as_played_label">Märgitud kuulatuks</string>
<string name="marked_as_unplayed_label">Märgitud kui kuulamata</string>
<string name="mark_read_no_media_label">Märgi loetuks</string>
<string name="play_this_to_seek_position">Asukohale hüppamiseks pead saadet esitama</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d saade märgiti kuulatuks.</item>
<item quantity="other">%d saadet märgiti kuulatuks.</item>
</plurals>
<string name="mark_unread_label">Märgitud kui kuulamata</string>
<string name="mark_unread_label_no_media">Märgi mitteloetuks</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d saade märgiti kui kuulamata.</item>
<item quantity="other">%d saadet märgiti kui kuulamata.</item>
</plurals>
<string name="add_to_queue_label">Lisa järjekorda</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d saade lisati järjekorda.</item>

View File

@ -187,23 +187,14 @@
<item quantity="other">%d deskargatutako kapituluak ezabatu egin dira</item>
</plurals>
<string name="removed_inbox_label">Sarrerako ontzitik kenduta</string>
<string name="mark_read_label">Markatu entzundakotzat</string>
<string name="toggle_played_label">Erreprodukzio-egoera txandakatu</string>
<string name="marked_as_played_label">Entzundakotzat markatua</string>
<string name="marked_as_unplayed_label">Ez entzundakotzat markatua</string>
<string name="mark_read_no_media_label">Markatu iraurrita gisa</string>
<string name="play_this_to_seek_position">Posizioetara jauzi egiteko, pasartea erreproduzitu behar duzu</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d saio markatuta ikusita bezala.</item>
<item quantity="other">%d saio markatuta ikusita bezala.</item>
</plurals>
<string name="mark_unread_label">Markatu ez entzundakotzat bezala</string>
<string name="mark_unread_label_no_media">Markatu ez irakurrita bezala</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d saio markatuta ikusita bezala.</item>
<item quantity="other">%d saio markatuta ez ikusita bezala.</item>
</plurals>
<string name="add_to_queue_label">Gehitu ilaran</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d saio ilaran gehitua.</item>

View File

@ -194,23 +194,14 @@
<item quantity="other">%dقسمت بار گرفته حذف شد.</item>
</plurals>
<string name="removed_inbox_label">حذف شده از صندوق ورودی</string>
<string name="mark_read_label">علامت‌گذاری به‌عنوان پخش‌شده</string>
<string name="toggle_played_label">وضعیت پخش را تغییر دهید</string>
<string name="marked_as_played_label">علامت‌گذاری شد به‌عنوان پخش‌شده</string>
<string name="marked_as_unplayed_label">علامت‌گذاری شد به‌عنوان پخش‌نشده</string>
<string name="mark_read_no_media_label">علامت زدن به عنوان خوانده شده</string>
<string name="play_this_to_seek_position">برای پرش به موقعیت‌ها، می‌توانید قسمت را پخش کنید</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d قسمت به‌عنوان پخش‌شده علامت‌گذاری شد.</item>
<item quantity="other">%d قسمت به‌عنوان پخش‌شده علامت‌گذاری شد.</item>
</plurals>
<string name="mark_unread_label">علامت‌گذاری به‌عنوان پخش‌نشده</string>
<string name="mark_unread_label_no_media">علامت‌گذاری به‌عنوان نخوانده‌شده</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d قسمت به‌عنوان پخش‌نشده علامت‌گذاری شد.</item>
<item quantity="other">%d قسمت به‌عنوان پخش‌نشده علامت‌گذاری شد.</item>
</plurals>
<string name="add_to_queue_label">افزودن به صف</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d قسمت به صف اضافه شد.</item>

View File

@ -187,23 +187,14 @@
<item quantity="other">%d ladattua jaksoa poistettu.</item>
</plurals>
<string name="removed_inbox_label">Poistettu saapuneista</string>
<string name="mark_read_label">Merkitse toistetuksi</string>
<string name="toggle_played_label">Vaihda toistettu-tila</string>
<string name="marked_as_played_label">Merkattu toistetuksi</string>
<string name="marked_as_unplayed_label">Merkattu toistamattomaksi</string>
<string name="mark_read_no_media_label">Merkitse luetuksi</string>
<string name="play_this_to_seek_position">Jaksoa pitää toistaa siirtyäksesi kohtiin</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d jakso merkitty soitetuksi.</item>
<item quantity="other">%d jaksoa merkitty toistetuksi</item>
</plurals>
<string name="mark_unread_label">Merkitse toistamattomaksi</string>
<string name="mark_unread_label_no_media">Merkitse lukemattomaksi</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d jakso merkitty soittamattomaksi.</item>
<item quantity="other">%d jaksoa merkitty toistamattomaksi.</item>
</plurals>
<string name="add_to_queue_label">Lisää jonoon</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d jakso lisätty jonoon.</item>

View File

@ -212,25 +212,14 @@
<item quantity="other">%d épisodes téléchargés supprimés.</item>
</plurals>
<string name="removed_inbox_label">Supprimé de la boîte de réception</string>
<string name="mark_read_label">Marquer comme lu</string>
<string name="toggle_played_label">Modifier l\'état de lecture</string>
<string name="marked_as_played_label">Marqué comme lu</string>
<string name="marked_as_unplayed_label">Marqué comme non lu</string>
<string name="mark_read_no_media_label">Marquer comme lu</string>
<string name="play_this_to_seek_position">Pour changer la position l\'épisode doit être en cours de lecture</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d épisode marqué comme lu.</item>
<item quantity="many">%d épisodes marqués comme lus.</item>
<item quantity="other">%d épisodes marqués comme lus.</item>
</plurals>
<string name="mark_unread_label">Marquer comme non lu</string>
<string name="mark_unread_label_no_media">Marquer comme non lu</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d épisode marqué comme non lu.</item>
<item quantity="many">%d épisodes marqués comme non lus.</item>
<item quantity="other">%d épisodes marqués comme non lus.</item>
</plurals>
<string name="add_to_queue_label">Ajouter à la liste de lecture</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d épisode ajouté à la liste de lecture.</item>

View File

@ -199,23 +199,13 @@
<item quantity="other">Eliminados %d episodios descargados.</item>
</plurals>
<string name="removed_inbox_label">Retirado da lista de novidades</string>
<string name="mark_read_label">Marcar como reproducido</string>
<string name="toggle_played_label">Cambiar estado de reprodución</string>
<string name="marked_as_played_label">Marcado como reproducido</string>
<string name="marked_as_unplayed_label">Marcado como non reproducido</string>
<string name="mark_read_no_media_label">Marcar como lido</string>
<string name="play_this_to_seek_position">Para ir á posición, debes reproducir o episodio</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episodio marcado como reproducido.</item>
<item quantity="other">%d episodios marcados como reproducidos.</item>
</plurals>
<string name="mark_unread_label">Marcar como non reproducido</string>
<string name="mark_unread_label_no_media">Marcar como non lido</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episodio marcado como non reproducido.</item>
<item quantity="other">%d episodios marcados como non reproducidos.</item>
</plurals>
<string name="add_to_queue_label">Engadir á cola</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episodio engadido a cola.</item>

View File

@ -175,23 +175,14 @@
<item quantity="other">%d letöltött epizód törölve.</item>
</plurals>
<string name="removed_inbox_label">Eltávolítva a beérkezők közül</string>
<string name="mark_read_label">Megjelölés lejátszottként</string>
<string name="toggle_played_label">Lejátszott állapot be/ki</string>
<string name="marked_as_played_label">Megjelölve lejátszottként</string>
<string name="marked_as_unplayed_label">Megjelölve nem lejátszottként</string>
<string name="mark_read_no_media_label">Megjelölés olvasottként</string>
<string name="play_this_to_seek_position">A pozíciókra ugráshoz le kell játszania az epizódot</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d epizód megjelölve lejátszottként.</item>
<item quantity="other">%d epizód megjelölve lejátszottként.</item>
</plurals>
<string name="mark_unread_label">Megjelölés nem lejátszottként</string>
<string name="mark_unread_label_no_media">Megjelölés olvasatlanként</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d epizód megjelölve nem lejátszottként.</item>
<item quantity="other">%d epizód megjelölve nem lejátszottként.</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d epizód sorbaállítva.</item>
<item quantity="other">%d epizód sorbaállítva.</item>

View File

@ -155,21 +155,14 @@
<item quantity="other">%depisode terunduh dihapus.</item>
</plurals>
<string name="removed_inbox_label">Dihapus dari kotak masuk</string>
<string name="mark_read_label">Tandai diputar</string>
<string name="toggle_played_label">Aktifkan status diputar</string>
<string name="marked_as_played_label">Ditandai sebagai \'diputar\'</string>
<string name="marked_as_unplayed_label">Ditandai sebagai \'belum diputar\'</string>
<string name="mark_read_no_media_label">Tandai dibaca</string>
<string name="play_this_to_seek_position">Untuk melompat posisi waktu, Anda perlu memutar episode terlebih dahulu</string>
<plurals name="marked_read_batch_label">
<item quantity="other">%d episode ditandai sebagai telah diputar.</item>
</plurals>
<string name="mark_unread_label">Tandai belum diputar</string>
<string name="mark_unread_label_no_media">Tandai belum dibaca</string>
<plurals name="marked_unread_batch_label">
<item quantity="other">%d episode ditandai sebagai belum diputar</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="other">%d episode ditambahkan ke antrian.</item>
</plurals>

View File

@ -212,25 +212,14 @@
<item quantity="other">%d episodi scaricati eliminati.</item>
</plurals>
<string name="removed_inbox_label">Rimosso dall\'inbox</string>
<string name="mark_read_label">Segna come riprodotto</string>
<string name="toggle_played_label">Cambia stato di riproduzione</string>
<string name="marked_as_played_label">Segnato come riprodotto</string>
<string name="marked_as_unplayed_label">Segnato come non riprodotto</string>
<string name="mark_read_no_media_label">Segna come letto</string>
<string name="play_this_to_seek_position">Per saltare alla posizione devi riprodurre l\'episodio</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episodio segnato come riprodotto.</item>
<item quantity="many">%d di episodi segnati come riprodotti.</item>
<item quantity="other">%d episodi segnati come riprodotti.</item>
</plurals>
<string name="mark_unread_label">Segna come non riprodotto</string>
<string name="mark_unread_label_no_media">Segna come non letto</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episodio segnato come non riprodotto.</item>
<item quantity="many">%d di episodi segnati come non riprodotti.</item>
<item quantity="other">%d episodi segnati come non riprodotti.</item>
</plurals>
<string name="add_to_queue_label">Aggiungi alla coda</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episodio aggiunto alla coda.</item>

View File

@ -217,27 +217,14 @@
<item quantity="other">%d פרקים שהורדו נמחקו.</item>
</plurals>
<string name="removed_inbox_label">הוסר מהדואר הנכנס</string>
<string name="mark_read_label">סימון כנצפה</string>
<string name="toggle_played_label">החלפת מצב נגינה</string>
<string name="marked_as_played_label">סומן כהתנגן</string>
<string name="marked_as_unplayed_label">סומן שלא התנגן</string>
<string name="mark_read_no_media_label">סימון כנקרא</string>
<string name="play_this_to_seek_position">כדי לקפוץ למיקומים, עליך לנגן את הפרק</string>
<plurals name="marked_read_batch_label">
<item quantity="one">פרק אחד סומן שנוגן.</item>
<item quantity="two">%d פרקים סומנו שנוגנו.</item>
<item quantity="many">%d פרקים סומנו שנוגנו.</item>
<item quantity="other">%d פרקים סומנו שנוגנו.</item>
</plurals>
<string name="mark_unread_label">סימון כלא נוגן</string>
<string name="mark_unread_label_no_media">סימון כלא נקרא</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">פרק אחד סומן שטרם נוגן.</item>
<item quantity="two">%d פרקים סומנו שטרם נוגנו.</item>
<item quantity="many">%d פרקים סומנו שטרם נוגנו.</item>
<item quantity="other">%d פרקים סומנו שטרם נוגנו.</item>
</plurals>
<string name="add_to_queue_label">הוספה לתור</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">פרק אחד נוסף לתור.</item>

View File

@ -147,21 +147,13 @@
<item quantity="other">ダウンロードした %d 項目のエピソードが削除されました。</item>
</plurals>
<string name="removed_inbox_label">受信トレイから取り除きました</string>
<string name="mark_read_label">再生済としてマーク</string>
<string name="toggle_played_label">再生状態の切り替え</string>
<string name="marked_as_played_label">再生済みとマーク済み</string>
<string name="marked_as_unplayed_label">未再生としてマーク</string>
<string name="mark_read_no_media_label">既読としてマーク</string>
<string name="play_this_to_seek_position">位置にジャンプするには、エピソードを再生する必要があります</string>
<plurals name="marked_read_batch_label">
<item quantity="other">%d エピソードを再生済にしました。</item>
</plurals>
<string name="mark_unread_label">未再生としてマーク</string>
<string name="mark_unread_label_no_media">未読としてマーク</string>
<plurals name="marked_unread_batch_label">
<item quantity="other">%d エピソードを未再生にしました。</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="other">%d エピソードをキューに追加しました。</item>
</plurals>

View File

@ -178,21 +178,14 @@
<item quantity="other">다운로드한 %d개 에피소드 삭제함.</item>
</plurals>
<string name="removed_inbox_label">새로 받음 목록에서 제거함</string>
<string name="mark_read_label">재생했다고 표시</string>
<string name="toggle_played_label">재생함 상태 토글</string>
<string name="marked_as_played_label">재생했다고 표시</string>
<string name="marked_as_unplayed_label">재생하지 않음으로 표시</string>
<string name="mark_read_no_media_label">읽었다고 표시</string>
<string name="play_this_to_seek_position">특정 재생 위치로 이동하려면, 에피소드를 재생해야 합니다</string>
<plurals name="marked_read_batch_label">
<item quantity="other">%d개 에피소드를 재생한 것으로 표시했습니다.</item>
</plurals>
<string name="mark_unread_label">재생하지 않음으로 표시</string>
<string name="mark_unread_label_no_media">읽지 않음으로 표시</string>
<plurals name="marked_unread_batch_label">
<item quantity="other">%d개 에피소드를 재생하지 않은 것으로 표시했습니다.</item>
</plurals>
<string name="add_to_queue_label">대기열에 추가</string>
<plurals name="added_to_queue_batch_label">
<item quantity="other">%d개 에피소드를 대기열에 추가했습니다.</item>

View File

@ -151,25 +151,12 @@
<item quantity="many">%d atsiųsti epizodai ištrinti.</item>
<item quantity="other">%d atsiųsti epizodai ištrinti.</item>
</plurals>
<string name="mark_read_label">Pažymėti kaip perklausytą</string>
<string name="marked_as_played_label">Pažymėtas kaip perklausytas</string>
<string name="marked_as_unplayed_label">Pažymėtas kaip neperklausytas</string>
<string name="mark_read_no_media_label">Pažymėti kaip perskaitytą</string>
<string name="play_this_to_seek_position">Norint peršokti į poziciją reikia pradėti epizodo atkūrimą</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d epizodas pažymėtas kaip perklausytas.</item>
<item quantity="few">%d epizodai pažymėti kaip perklausyti.</item>
<item quantity="many">%d epizodai pažymėti kaip perklausyti.</item>
<item quantity="other">%d epizodai pažymėti kaip perklausyti.</item>
</plurals>
<string name="mark_unread_label">Pažymėti kaip neperklausytą</string>
<string name="mark_unread_label_no_media">Pažymėti kaip neperskaitytą</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d epizodas pažymėtas kaip neperklausytas.</item>
<item quantity="few">%d epizodai pažymėti kaip neperklausyti.</item>
<item quantity="many">%d epizodai pažymėti kaip neperklausyti.</item>
<item quantity="other">%d epizodai pažymėti kaip neperklausyti.</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d epizodas pridėtas į eilę.</item>
<item quantity="few">%d epizodai pridėti į eilę.</item>

View File

@ -183,23 +183,14 @@
<item quantity="other">%d nedlasted episoder slettet.</item>
</plurals>
<string name="removed_inbox_label">Fjernet fra innboksen</string>
<string name="mark_read_label">Marker som avspilt</string>
<string name="toggle_played_label">Bytte avspilt status</string>
<string name="marked_as_played_label">Marker som avspilt</string>
<string name="marked_as_unplayed_label">Marker som ikke avspilt</string>
<string name="mark_read_no_media_label">Merk som lest</string>
<string name="play_this_to_seek_position">For å hoppe til posisjoner, må du spille episoden</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episode merket som avspilt.</item>
<item quantity="other">%d episoder merket som avspilt.</item>
</plurals>
<string name="mark_unread_label">Marker som ikke avspilt</string>
<string name="mark_unread_label_no_media">Merk som ulest</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episode merket som ikke avspilt.</item>
<item quantity="other">%d episoder merket som ikke avspilt.</item>
</plurals>
<string name="add_to_queue_label">Legg til i kø</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episode lagt til i køen.</item>

View File

@ -193,23 +193,14 @@
<item quantity="other">%d gedownloade afleveringen verwijderd.</item>
</plurals>
<string name="removed_inbox_label">Verwijderd uit Postvak IN</string>
<string name="mark_read_label">Als afgespeeld markeren</string>
<string name="toggle_played_label">Afspeelstatus wijzigen</string>
<string name="marked_as_played_label">Gemarkeerd als afgespeeld</string>
<string name="marked_as_unplayed_label">Gemarkeerd als niet-afgespeeld</string>
<string name="mark_read_no_media_label">Markeren als gelezen</string>
<string name="play_this_to_seek_position">Speel de aflevering af om naar posities te springen</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d aflevering gemarkeerd als afgespeeld.</item>
<item quantity="other">%d afleveringen gemarkeerd als afgespeeld.</item>
</plurals>
<string name="mark_unread_label">Als niet-afgespeeld markeren</string>
<string name="mark_unread_label_no_media">Markeren als ongelezen</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d aflevering gemarkeerd als niet-afgespeeld.</item>
<item quantity="other">%d afleveringen gemarkeerd als niet-afgespeeld.</item>
</plurals>
<string name="add_to_queue_label">Toevoegen aan wachtrij</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d aflevering toegevoegd aan de wachtrij.</item>

View File

@ -204,27 +204,14 @@
<item quantity="other">Usunięto %d pobranych odcinków.</item>
</plurals>
<string name="removed_inbox_label">Usunięto ze skrzynki odbiorczej</string>
<string name="mark_read_label">Oznacz jako odtworzone</string>
<string name="toggle_played_label">Pokaż status odtworzenia</string>
<string name="marked_as_played_label">Oznaczone jako odtworzone</string>
<string name="marked_as_unplayed_label">Oznaczone jako nieodtworzone</string>
<string name="mark_read_no_media_label">Oznacz jako przeczytane</string>
<string name="play_this_to_seek_position">Aby skoczyć do konkretnej pozycji, musisz odtworzyć odcinek</string>
<plurals name="marked_read_batch_label">
<item quantity="one">Oznaczono %d odcinek jako odtworzony.</item>
<item quantity="few">Oznaczono %d odcinki(ów) jako odtworzone.</item>
<item quantity="many">Oznaczono %d odcinki(ów) jako odtworzone.</item>
<item quantity="other">Oznaczono %d odcinki(ów) jako odtworzone.</item>
</plurals>
<string name="mark_unread_label">Oznacz jako nieodtworzone</string>
<string name="mark_unread_label_no_media">Oznacz jako nieprzeczytane</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">Oznaczono %d odcinek jako nieodtworzony.</item>
<item quantity="few">Oznaczono %d odcinki(ów) jako nieodtworzone.</item>
<item quantity="many">Oznaczono %d odcinki(ów) jako nieodtworzone.</item>
<item quantity="other">Oznaczono %d odcinki(ów) jako nieodtworzone.</item>
</plurals>
<string name="add_to_queue_label">Dodaj do kolejki</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d odcinek dodano do kolejki.</item>

View File

@ -199,25 +199,14 @@
<item quantity="other">%d episódios baixados deletados.</item>
</plurals>
<string name="removed_inbox_label">Removido da caixa de entrada</string>
<string name="mark_read_label">Marcar como reproduzido</string>
<string name="toggle_played_label">Alterar estado de reprodução</string>
<string name="marked_as_played_label">Marcado como reproduzido</string>
<string name="marked_as_unplayed_label">Marcado como não reproduzido</string>
<string name="mark_read_no_media_label">Marcar como lido</string>
<string name="play_this_to_seek_position">Para pular para as posições, você precisa reproduzir o episódio</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episódio marcado como reproduzido.</item>
<item quantity="many">%d episódios marcados como reproduzidos.</item>
<item quantity="other">%d episódios marcados como reproduzidos.</item>
</plurals>
<string name="mark_unread_label">Marcar como não reproduzido</string>
<string name="mark_unread_label_no_media">Marcar como não reproduzido</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episódio marcado como não reproduzido.</item>
<item quantity="many">%d episódios marcados como não reproduzidos.</item>
<item quantity="other">%d episódios marcados como não reproduzidos.</item>
</plurals>
<string name="add_to_queue_label">Adicionar à fila</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episódio adicionado à fila.</item>

View File

@ -211,25 +211,14 @@
<item quantity="other">%d episódios eliminados</item>
</plurals>
<string name="removed_inbox_label">Removido da caixa de entrada</string>
<string name="mark_read_label">Marcar como reproduzido</string>
<string name="toggle_played_label">Alternar estado de reprodução</string>
<string name="marked_as_played_label">Marcado como reproduzido</string>
<string name="marked_as_unplayed_label">Marcado como não reproduzido</string>
<string name="mark_read_no_media_label">Marcar como lido</string>
<string name="play_this_to_seek_position">Se quiser ir para uma posição, tem que reproduzir o episódio</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d episódio marcado como reproduzido</item>
<item quantity="many">%d episódios marcados como reproduzidos</item>
<item quantity="other">%d episódios marcados como reproduzidos</item>
</plurals>
<string name="mark_unread_label">Marcar como não reproduzido</string>
<string name="mark_unread_label_no_media">Marcar como não lido</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d episódio marcado como não reproduzido</item>
<item quantity="many">%d episódios marcados como não reproduzidos</item>
<item quantity="other">%d episódios marcados como não reproduzidos</item>
</plurals>
<string name="add_to_queue_label">Adicionar à fila</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d episódio adicionado à fila</item>

View File

@ -208,25 +208,14 @@
<item quantity="other">%d de episoade descărcare au fost șterse.</item>
</plurals>
<string name="removed_inbox_label">Eliminat din inbox</string>
<string name="mark_read_label">Marchează ca redat</string>
<string name="toggle_played_label">Comută starea de redare</string>
<string name="marked_as_played_label">Marcat ca redat</string>
<string name="marked_as_unplayed_label">Marcat ca neredat</string>
<string name="mark_read_no_media_label">Marcați ca citit</string>
<string name="play_this_to_seek_position">Pentru a sări către o poziție, trebuie sa începeți redarea episodului</string>
<plurals name="marked_read_batch_label">
<item quantity="one">Un episod marcat ca redat.</item>
<item quantity="few">%d episoade marcate ca redate.</item>
<item quantity="other">%d de episoade marcate ca redate.</item>
</plurals>
<string name="mark_unread_label">Marchează ca neredat</string>
<string name="mark_unread_label_no_media">Marchează ca necitit.</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">Un episod marcat ca neredat.</item>
<item quantity="few">%d episoade marcate ca neredate.</item>
<item quantity="other">%d de episoade marcate ca neredate.</item>
</plurals>
<string name="add_to_queue_label">Adaugă la coadă</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">Un episod adăugat la coadă.</item>

View File

@ -205,27 +205,14 @@
<item quantity="other">%d загруженных выпусков удалёно.</item>
</plurals>
<string name="removed_inbox_label">Убрано из входящих</string>
<string name="mark_read_label">Отметить как прослушанное</string>
<string name="toggle_played_label">Переключить состояние \"прослушано\"</string>
<string name="marked_as_played_label">Отмечено как прослушанное</string>
<string name="marked_as_unplayed_label">Отметить как непрослушанное</string>
<string name="mark_read_no_media_label">Отметить как прочитанное</string>
<string name="play_this_to_seek_position">Для переходов в выпуске нужно слушать выпуск</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d выпуск отмечен как прослушанный.</item>
<item quantity="few">%d выпуска отмечены как прослушанные.</item>
<item quantity="many">%d выпусков отмечены как прослушанные.</item>
<item quantity="other">%d выпусков отмечено как прослушанные.</item>
</plurals>
<string name="mark_unread_label">Отметить как непрослушанное</string>
<string name="mark_unread_label_no_media">Отметить как непрочитанное</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d выпуск отмечен непрослушанный.</item>
<item quantity="few">%d выпуска отмечены непрослушанные.</item>
<item quantity="many">%d выпусков отмечены непрослушанные.</item>
<item quantity="other">%d выпусков отмечено непрослушанные.</item>
</plurals>
<string name="add_to_queue_label">Добавить в очередь</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d выпуск добавлен в очередь.</item>

View File

@ -217,27 +217,14 @@
<item quantity="other">%dstiahnutých epizód zmazaných.</item>
</plurals>
<string name="removed_inbox_label">Odstránené zo schránky</string>
<string name="mark_read_label">Označiť ako prehrané</string>
<string name="toggle_played_label">Prepnúť stav prehratia</string>
<string name="marked_as_played_label">Označené ako prehrané</string>
<string name="marked_as_unplayed_label">Označené ako neprehrané</string>
<string name="mark_read_no_media_label">Označiť ako prečítané</string>
<string name="play_this_to_seek_position">Preskočenie na určitú pozíciu funguje len pri prehrávaní epizódy.</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d epizóda bola označená ako prehraná.</item>
<item quantity="few">%d epizódy boli označené ako prehrané.</item>
<item quantity="many">%d epizód bolo označených ako prehrané.</item>
<item quantity="other">%d epizód bolo označených ako prehrané.</item>
</plurals>
<string name="mark_unread_label">Označiť ako neprehrané</string>
<string name="mark_unread_label_no_media">Označiť ako prehrané</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d epizóda bola označená ako neprehraná.</item>
<item quantity="few">%d epizódy bolo označené ako neprehrané.</item>
<item quantity="many">%d epizód bolo označených ako neprehrané.</item>
<item quantity="other">%d epizód bolo označených ako neprehrané.</item>
</plurals>
<string name="add_to_queue_label">Pridať do poradia</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d epizóda bola pridaná do poradia</item>

View File

@ -142,25 +142,12 @@
<string name="stream_label">Tok</string>
<string name="delete_label">Briši</string>
<string name="delete_failed">Ni bilo mogoče brisati datoteke. Ponovni zagon naprave bi lahko pomagal.</string>
<string name="mark_read_label">Označi kot predvajan</string>
<string name="marked_as_played_label">Označi kot predvajano.</string>
<string name="marked_as_unplayed_label">Označi kot ne predvajano.</string>
<string name="mark_read_no_media_label">Označi kot prebrano</string>
<string name="play_this_to_seek_position">Če želite skočiti na to mesto, morate predvajati epizodo </string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d označi kot predvajano.</item>
<item quantity="two">%d označi kot predvajani.</item>
<item quantity="few"> %d označi kot predvajano.</item>
<item quantity="other">%d označi kot predvajano.</item>
</plurals>
<string name="mark_unread_label">Označi kot ne predvajano.</string>
<string name="mark_unread_label_no_media">Pusti ne prebrano.</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d epizodo označi kot ne predvajano.</item>
<item quantity="two">%depizodi označi kot ne predvajani.</item>
<item quantity="few">%d epizode označi kot ne predvajane.</item>
<item quantity="other">%d epizode označi kot ne predvajane.</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d epizoda je dodana v čakalno vrsto.</item>
<item quantity="two">%d epizodi sta dodani v čakalno vrsto.</item>

View File

@ -199,23 +199,14 @@
<item quantity="other">%d nedladdade episoder borttagna.</item>
</plurals>
<string name="removed_inbox_label">Tog bort från inkorg</string>
<string name="mark_read_label">Markera som spelad</string>
<string name="toggle_played_label">Växla spelläge</string>
<string name="marked_as_played_label">Markera som spelad</string>
<string name="marked_as_unplayed_label">Markera som ospelad</string>
<string name="mark_read_no_media_label">Markera som läst</string>
<string name="play_this_to_seek_position">Spela episoden för att kunna hoppa till olika positioner</string>
<plurals name="marked_read_batch_label">
<item quantity="one">1%d episod markerad som spelad.</item>
<item quantity="other">%d episoder markerade som spelade.</item>
</plurals>
<string name="mark_unread_label">Markera som ospelad</string>
<string name="mark_unread_label_no_media">Markera som ospelad</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">1%d episod markerad som ospelad.</item>
<item quantity="other">%d episoder markerade som ospelade.</item>
</plurals>
<string name="add_to_queue_label">Lägg till i kön</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">1%d episod tillagd i kön.</item>

View File

@ -192,23 +192,14 @@
<item quantity="other">%d indirilmiş bölüm silindi.</item>
</plurals>
<string name="removed_inbox_label">Gelen kutusundan silindi</string>
<string name="mark_read_label">Oynatıldı olarak işaretle</string>
<string name="toggle_played_label">Oynatma durumunu değiştir</string>
<string name="marked_as_played_label">Çalındı olarak işaretlendi</string>
<string name="marked_as_unplayed_label">Çalınmadı olarak işaretlendi</string>
<string name="mark_read_no_media_label">Okundu olarak işaretle</string>
<string name="play_this_to_seek_position">Başka bir konuma atlamak için bölümü oynatmanız gerekir.</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d bölüm oynatıldı olarak işaretlendi.</item>
<item quantity="other">%d bölüm oynatıldı olarak işaretlendi. </item>
</plurals>
<string name="mark_unread_label">Oynatılmadı olarak işaretle</string>
<string name="mark_unread_label_no_media">Okunmadı olarak işaretle</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d bölüm oynanmamış olarak işaretlendi.</item>
<item quantity="other">%d bölüm oynanmamış olarak işaretlendi.</item>
</plurals>
<string name="add_to_queue_label">Sıraya ekle</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d bölüm kuyruğa eklendi.</item>

View File

@ -217,27 +217,14 @@
<item quantity="other">%d завантажених епізодів видалено.</item>
</plurals>
<string name="removed_inbox_label">Вилучено з вхідних</string>
<string name="mark_read_label">Позначити як відтворений</string>
<string name="toggle_played_label">Перемикнути стан прослуханості</string>
<string name="marked_as_played_label">Позначено як відтворений</string>
<string name="marked_as_unplayed_label">Позначено як невідтворений</string>
<string name="mark_read_no_media_label">Позначити як прочитане</string>
<string name="play_this_to_seek_position">Щоб перейти до позиції, потрібно відтворити епізод</string>
<plurals name="marked_read_batch_label">
<item quantity="one">%d епізод помічено як відтворений</item>
<item quantity="few">%d епізоди помічено як відтворені</item>
<item quantity="many">%d епізодів помічено як відтворені</item>
<item quantity="other">%d епізодів помічено як відтворені</item>
</plurals>
<string name="mark_unread_label">Позначити як невідтворений</string>
<string name="mark_unread_label_no_media">Позначити як непрочитане</string>
<plurals name="marked_unread_batch_label">
<item quantity="one">%d епізод помічено як невідтворений.</item>
<item quantity="few">%d епізоди помічено як невідтворені.</item>
<item quantity="many">%d епізодів помічено як невідтворені.</item>
<item quantity="other">%d епізодів помічено як невідтворені.</item>
</plurals>
<string name="add_to_queue_label">Додати до черги</string>
<plurals name="added_to_queue_batch_label">
<item quantity="one">%d епізод додано до черги.</item>

View File

@ -194,21 +194,14 @@
<item quantity="other">删除了 %d 个已下载的节目。</item>
</plurals>
<string name="removed_inbox_label">已从收件箱删除</string>
<string name="mark_read_label">标记已播放</string>
<string name="toggle_played_label">切换播放状态</string>
<string name="marked_as_played_label">标记为已播放</string>
<string name="marked_as_unplayed_label">标记为未播放</string>
<string name="mark_read_no_media_label">标记为已读</string>
<string name="play_this_to_seek_position">要跳转到某处,你需要播放这一集</string>
<plurals name="marked_read_batch_label">
<item quantity="other">已将%d个节目标记为已播放</item>
</plurals>
<string name="mark_unread_label">标记未播放</string>
<string name="mark_unread_label_no_media">标为未读</string>
<plurals name="marked_unread_batch_label">
<item quantity="other">已将%d个节目标记为未播放</item>
</plurals>
<string name="add_to_queue_label">加至队列</string>
<plurals name="added_to_queue_batch_label">
<item quantity="other">已将%d个节目添加到序列中</item>

View File

@ -105,17 +105,10 @@
<string name="stream_label">串流播放</string>
<string name="delete_label">刪除</string>
<string name="delete_failed">刪除文件失敗。重啟設備試試看。</string>
<string name="mark_read_label">標記為已播放</string>
<string name="mark_read_no_media_label">標示為已讀</string>
<string name="play_this_to_seek_position">若想指定播放位置,請先播放該單集</string>
<plurals name="marked_read_batch_label">
<item quantity="other">共有 %d 集標示為已播放。</item>
</plurals>
<string name="mark_unread_label">標記為未播放</string>
<string name="mark_unread_label_no_media">標示為未讀</string>
<plurals name="marked_unread_batch_label">
<item quantity="other">共有 %d 集標示為未播放。</item>
</plurals>
<plurals name="added_to_queue_batch_label">
<item quantity="other">已將 %d 集加入待播清單。</item>
</plurals>

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