6.11.7 commit
This commit is contained in:
parent
29f7c6ce87
commit
a9f59905bd
|
@ -31,8 +31,8 @@ android {
|
|||
testApplicationId "ac.mdiq.podcini.tests"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
versionCode 3020276
|
||||
versionName "6.11.6"
|
||||
versionCode 3020277
|
||||
versionName "6.11.7"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
|
|
@ -184,6 +184,15 @@ object Feeds {
|
|||
return null
|
||||
}
|
||||
|
||||
fun isSubscribed(feed: Feed): Boolean {
|
||||
val f = realm.query(Feed::class, "eigenTitle == $0 && author == $1", feed.eigenTitle, feed.author).first().find()
|
||||
return f != null
|
||||
}
|
||||
|
||||
fun getFeedByTitleAndAuthor(title: String, author: String): Feed? {
|
||||
return realm.query(Feed::class, "eigenTitle == $0 && author == $1", title, author).first().find()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new Feeds to the database or updates the old versions if they already exists. If another Feed with the same
|
||||
* identifying value already exists, this method will add new FeedItems from the new Feed to the existing Feed.
|
||||
|
@ -271,6 +280,7 @@ object Feeds {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (oldItem != null) oldItem.updateFromOther(episode)
|
||||
else {
|
||||
Logd(TAG, "Found new episode: ${episode.title}")
|
||||
|
@ -316,6 +326,11 @@ object Feeds {
|
|||
savedFeed.type = newFeed.type
|
||||
savedFeed.lastUpdateFailed = false
|
||||
resultFeed = savedFeed
|
||||
|
||||
savedFeed.totleDuration = 0
|
||||
for (e in savedFeed.episodes) {
|
||||
savedFeed.totleDuration += e.media?.duration ?: 0
|
||||
}
|
||||
try {
|
||||
upsertBlk(savedFeed) {}
|
||||
if (removeUnlistedItems && unlistedItems.isNotEmpty()) runBlocking { deleteEpisodes(context, unlistedItems).join() }
|
||||
|
@ -357,13 +372,13 @@ object Feeds {
|
|||
feed.preferences = FeedPreferences(feed.id, false, AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "")
|
||||
else feed.preferences!!.feedID = feed.id
|
||||
|
||||
feed.totleDuration = 0
|
||||
Logd(TAG, "feed.episodes count: ${feed.episodes.size}")
|
||||
for (episode in feed.episodes) {
|
||||
episode.id = idLong++
|
||||
episode.feedId = feed.id
|
||||
if (episode.media != null) episode.media!!.id = episode.id
|
||||
// copyToRealm(episode) // no need if episodes is a relation of feed, otherwise yes.
|
||||
// idLong += 1
|
||||
feed.totleDuration += episode.media?.duration ?: 0
|
||||
}
|
||||
copyToRealm(feed)
|
||||
}
|
||||
|
@ -458,7 +473,7 @@ object Feeds {
|
|||
return 1
|
||||
}
|
||||
|
||||
fun createSynthetic(feedId: Long, name: String): Feed {
|
||||
fun createSynthetic(feedId: Long, name: String, video: Boolean = false): Feed {
|
||||
val feed = Feed()
|
||||
var feedId_ = feedId
|
||||
if (feedId_ <= 0) {
|
||||
|
@ -473,6 +488,7 @@ object Feeds {
|
|||
feed.title = name
|
||||
feed.author = "Yours Truly"
|
||||
feed.downloadUrl = null
|
||||
feed.hasVideoMedia = video
|
||||
feed.fileUrl = File(feedfilePath, getFeedfileName(feed)).toString()
|
||||
feed.preferences = FeedPreferences(feed.id, false, FeedPreferences.AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "")
|
||||
feed.preferences!!.keepUpdated = false
|
||||
|
|
|
@ -40,7 +40,7 @@ object RealmDB {
|
|||
SubscriptionLog::class,
|
||||
Chapter::class))
|
||||
.name("Podcini.realm")
|
||||
.schemaVersion(26)
|
||||
.schemaVersion(27)
|
||||
.migration({ mContext ->
|
||||
val oldRealm = mContext.oldRealm // old realm using the previous schema
|
||||
val newRealm = mContext.newRealm // new realm using the new schema
|
||||
|
|
|
@ -69,6 +69,8 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
private set
|
||||
|
||||
// if null: unknown, will be checked
|
||||
// TODO: what to do with this? can be expensive
|
||||
@Ignore
|
||||
var hasEmbeddedPicture: Boolean? = null
|
||||
|
||||
@Ignore
|
||||
|
@ -78,10 +80,6 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
// var episodeId: Long = 0
|
||||
// private set
|
||||
|
||||
// @Ignore
|
||||
// val isInProgress: Boolean
|
||||
// get() = (this.position > 0)
|
||||
|
||||
constructor() {}
|
||||
|
||||
constructor(i: Episode?, downloadUrl: String?, size: Long, mimeType: String?) {
|
||||
|
@ -215,14 +213,13 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
|
||||
fun hasEmbeddedPicture(): Boolean {
|
||||
// TODO: checkEmbeddedPicture needs to update current copy
|
||||
if (hasEmbeddedPicture == null) unmanaged(this).checkEmbeddedPicture()
|
||||
if (hasEmbeddedPicture == null) checkEmbeddedPicture()
|
||||
return hasEmbeddedPicture ?: false
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(id.toString())
|
||||
dest.writeString(if (episode != null) episode!!.id.toString() else "")
|
||||
|
||||
dest.writeInt(duration)
|
||||
dest.writeInt(position)
|
||||
dest.writeLong(size)
|
||||
|
@ -329,7 +326,7 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
|
|||
hasEmbeddedPicture = false
|
||||
}
|
||||
}
|
||||
if (persist && episode != null) upsertBlk(episode!!) {}
|
||||
// if (persist && episode != null) upsertBlk(episode!!) {}
|
||||
}
|
||||
|
||||
fun episodeOrFetch(): Episode? {
|
||||
|
|
|
@ -89,6 +89,8 @@ class Feed : RealmObject {
|
|||
|
||||
var preferences: FeedPreferences? = null
|
||||
|
||||
var totleDuration: Long = 0L
|
||||
|
||||
// TODO: this might not be needed
|
||||
var measures: FeedMeasures? = null
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ class ShareLog : RealmObject {
|
|||
|
||||
var url: String? = null
|
||||
|
||||
var title: String? = null
|
||||
|
||||
var author: String? = null
|
||||
|
||||
var type: String? = null
|
||||
|
||||
var status: Int = Status.ERROR.ordinal
|
||||
|
@ -23,6 +27,12 @@ class ShareLog : RealmObject {
|
|||
this.url = url
|
||||
}
|
||||
|
||||
enum class Type {
|
||||
Text,
|
||||
YTMedia,
|
||||
Podcast
|
||||
}
|
||||
|
||||
enum class Status {
|
||||
ERROR,
|
||||
SUCCESS,
|
||||
|
|
|
@ -6,6 +6,7 @@ import ac.mdiq.podcini.storage.model.Playable
|
|||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.Prefs.prefEpisodeCover
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
|
||||
/**
|
||||
* Utility class to use the appropriate image resource based on [UserPreferences].
|
||||
|
@ -31,6 +32,7 @@ object ImageResourceUtils {
|
|||
*/
|
||||
@JvmStatic
|
||||
fun getEpisodeListImageLocation(episode: Episode): String? {
|
||||
Logd("ImageResourceUtils", "getEpisodeListImageLocation called")
|
||||
return if (useEpisodeCoverSetting) episode.imageLocation
|
||||
else getFallbackImageLocation(episode)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,9 @@ import androidx.compose.material3.*
|
|||
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.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.text.HtmlCompat
|
||||
|
@ -87,31 +89,31 @@ abstract class EpisodeActionButton internal constructor(@JvmField var item: Epis
|
|||
IconButton(onClick = {
|
||||
PlayActionButton(item).onClick(context)
|
||||
onDismiss()
|
||||
}) { Image(painter = painterResource(R.drawable.ic_play_24dp), contentDescription = "Play") }
|
||||
}) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_play_24dp), contentDescription = "Play") }
|
||||
}
|
||||
if (label != R.string.stream_label && label != R.string.play_label && label != R.string.pause_label && label != R.string.delete_label) {
|
||||
IconButton(onClick = {
|
||||
StreamActionButton(item).onClick(context)
|
||||
onDismiss()
|
||||
}) { Image(painter = painterResource(R.drawable.ic_stream), contentDescription = "Stream") }
|
||||
}) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_stream), contentDescription = "Stream") }
|
||||
}
|
||||
if (label != R.string.download_label && label != R.string.play_label && label != R.string.delete_label) {
|
||||
IconButton(onClick = {
|
||||
DownloadActionButton(item).onClick(context)
|
||||
onDismiss()
|
||||
}) { Image(painter = painterResource(R.drawable.ic_download), contentDescription = "Download") }
|
||||
}) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_download), contentDescription = "Download") }
|
||||
}
|
||||
if (label != R.string.delete_label && label != R.string.download_label && label != R.string.stream_label) {
|
||||
IconButton(onClick = {
|
||||
DeleteActionButton(item).onClick(context)
|
||||
onDismiss()
|
||||
}) { Image(painter = painterResource(R.drawable.ic_delete), contentDescription = "Delete") }
|
||||
}) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_delete), contentDescription = "Delete") }
|
||||
}
|
||||
if (label != R.string.visit_website_label) {
|
||||
IconButton(onClick = {
|
||||
VisitWebsiteActionButton(item).onClick(context)
|
||||
onDismiss()
|
||||
}) { Image(painter = painterResource(R.drawable.ic_web), contentDescription = "Web") }
|
||||
}) { Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_web), contentDescription = "Web") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -600,7 +600,7 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
}
|
||||
showPickerDialog = false
|
||||
}) {
|
||||
Icon(painter = painterResource(keys[index].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(keys[index].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp))
|
||||
Text(keys[index].getTitle(context), color = textColor, textAlign = TextAlign.Center)
|
||||
}
|
||||
}
|
||||
|
@ -644,22 +644,22 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
|
|||
Text(stringResource(R.string.swipe_left))
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(painter = painterResource(leftAction.value[0].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
Icon(imageVector = ImageVector.vectorResource(leftAction.value[0].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
.clickable(onClick = {
|
||||
direction = -1
|
||||
showPickerDialog = true
|
||||
})
|
||||
)
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(50.dp).height(35.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(50.dp).height(35.dp))
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
}
|
||||
Text(stringResource(R.string.swipe_right))
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, end = 10.dp)) {
|
||||
Spacer(Modifier.weight(0.5f))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(50.dp).height(35.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(50.dp).height(35.dp))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(painter = painterResource(rightAction.value[0].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
Icon(imageVector = ImageVector.vectorResource(rightAction.value[0].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
.clickable(onClick = {
|
||||
direction = 1
|
||||
showPickerDialog = true
|
||||
|
|
|
@ -681,7 +681,8 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
intent.hasExtra(Extras.fragment_feed_url.name) -> {
|
||||
val feedurl = intent.getStringExtra(Extras.fragment_feed_url.name)
|
||||
if (feedurl != null) loadChildFragment(OnlineFeedFragment.newInstance(feedurl))
|
||||
val isShared = intent.getBooleanExtra(Extras.isShared.name, false)
|
||||
if (feedurl != null) loadChildFragment(OnlineFeedFragment.newInstance(feedurl, isShared))
|
||||
}
|
||||
intent.hasExtra(Extras.search_string.name) -> {
|
||||
val query = intent.getStringExtra(Extras.search_string.name)
|
||||
|
@ -810,6 +811,7 @@ class MainActivity : CastEnabledActivity() {
|
|||
add_to_back_stack,
|
||||
generated_view_id,
|
||||
search_string,
|
||||
isShared
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -829,9 +831,10 @@ class MainActivity : CastEnabledActivity() {
|
|||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showOnlineFeed(context: Context, feedUrl: String): Intent {
|
||||
fun showOnlineFeed(context: Context, feedUrl: String, isShared: Boolean = false): Intent {
|
||||
val intent = Intent(context.applicationContext, MainActivity::class.java)
|
||||
intent.putExtra(Extras.fragment_feed_url.name, feedUrl)
|
||||
intent.putExtra(Extras.isShared.name, isShared)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
return intent
|
||||
}
|
||||
|
|
|
@ -111,22 +111,22 @@ class ShareReceiverActivity : AppCompatActivity() {
|
|||
when {
|
||||
// plain text
|
||||
sharedUrl.matches(Regex("^[^\\s<>/]+\$")) -> {
|
||||
if (log != null) upsertBlk(log) {it.type = "text" }
|
||||
if (log != null) upsertBlk(log) {it.type = ShareLog.Type.Text.name }
|
||||
val intent = MainActivity.showOnlineSearch(activity, sharedUrl)
|
||||
activity.startActivity(intent)
|
||||
if (finish) activity.finish()
|
||||
}
|
||||
// Youtube media
|
||||
(isYoutubeURL(url) && (url.path.startsWith("/watch") || url.path.startsWith("/live"))) || isYoutubeServiceURL(url) -> {
|
||||
if (log != null) upsertBlk(log) {it.type = "youtube media" }
|
||||
if (log != null) upsertBlk(log) {it.type = ShareLog.Type.YTMedia.name }
|
||||
Logd(TAG, "got youtube media")
|
||||
mediaCB()
|
||||
}
|
||||
// podcast or Youtube channel, Youtube playlist, or other?
|
||||
else -> {
|
||||
if (log != null) upsertBlk(log) {it.type = "podcast" }
|
||||
if (log != null) upsertBlk(log) {it.type = ShareLog.Type.Podcast.name }
|
||||
Logd(TAG, "Activity was started with url $sharedUrl")
|
||||
val intent = MainActivity.showOnlineFeed(activity, sharedUrl)
|
||||
val intent = MainActivity.showOnlineFeed(activity, sharedUrl, true)
|
||||
activity.startActivity(intent)
|
||||
if (finish) activity.finish()
|
||||
}
|
||||
|
|
|
@ -20,9 +20,11 @@ import androidx.compose.material3.Text
|
|||
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.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
@ -58,7 +60,7 @@ fun ChaptersDialog(media: Playable, onDismissRequest: () -> Unit) {
|
|||
Text(stringResource(R.string.chapter_duration0) + getDurationStringLocalized(LocalContext.current, duration), color = textColor)
|
||||
}
|
||||
val playRes = if (index == currentChapterIndex) R.drawable.ic_replay else R.drawable.ic_play_48dp
|
||||
Icon(painter = painterResource(playRes), tint = textColor, contentDescription = "play button",
|
||||
Icon(imageVector = ImageVector.vectorResource(playRes), tint = textColor, contentDescription = "play button",
|
||||
modifier = Modifier.width(28.dp).height(32.dp).clickable {
|
||||
if (MediaPlayerBase.status != PlayerStatus.PLAYING) playPause()
|
||||
seekTo(ch.start.toInt())
|
||||
|
|
|
@ -24,6 +24,7 @@ import ac.mdiq.podcini.storage.model.Feed.Companion.newId
|
|||
import ac.mdiq.podcini.storage.utils.DurationConverter
|
||||
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
|
||||
import ac.mdiq.podcini.ui.actions.EpisodeActionButton
|
||||
import ac.mdiq.podcini.ui.actions.EpisodeActionButton.Companion.forItem
|
||||
import ac.mdiq.podcini.ui.actions.SwipeAction
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
|
||||
|
@ -58,7 +59,6 @@ import androidx.compose.material.icons.filled.Edit
|
|||
import androidx.compose.material3.*
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
|
@ -67,7 +67,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.input.pointer.util.VelocityTracker
|
||||
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.TextStyle
|
||||
|
@ -79,7 +78,7 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import io.realm.kotlin.notifications.SingleQueryChange
|
||||
|
@ -95,15 +94,25 @@ fun InforBar(text: MutableState<String>, leftAction: MutableState<SwipeAction>,
|
|||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Logd("InforBar", "textState: ${text.value}")
|
||||
Row {
|
||||
Icon(painter = painterResource(leftAction.value.getActionIcon()), tint = textColor, contentDescription = "left_action_icon",
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "left_arrow", modifier = Modifier.width(24.dp).height(24.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(leftAction.value.getActionIcon()), tint = textColor, contentDescription = "left_action_icon",
|
||||
modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp)
|
||||
.clickable(onClick = actionConfig))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_left_alt_24), tint = textColor, contentDescription = "left_arrow", modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Text(text.value, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier.width(24.dp).height(24.dp))
|
||||
Icon(painter = painterResource(rightAction.value.getActionIcon()), tint = textColor, contentDescription = "right_action_icon",
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = actionConfig))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_right_alt_24), tint = textColor, contentDescription = "right_arrow", modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(rightAction.value.getActionIcon()), tint = textColor, contentDescription = "right_action_icon",
|
||||
modifier = Modifier
|
||||
.width(24.dp)
|
||||
.height(24.dp)
|
||||
.clickable(onClick = actionConfig))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,8 +126,8 @@ class EpisodeVM(var episode: Episode) {
|
|||
var ratingState by mutableIntStateOf(episode.rating)
|
||||
var inProgressState by mutableStateOf(episode.isInProgress)
|
||||
var downloadState by mutableIntStateOf(if (episode.media?.downloaded == true) DownloadStatus.State.COMPLETED.ordinal else DownloadStatus.State.UNKNOWN.ordinal)
|
||||
var actionButton by mutableStateOf<EpisodeActionButton?>(null)
|
||||
var actionRes by mutableIntStateOf(R.drawable.ic_questionmark)
|
||||
var actionButton by mutableStateOf(forItem(episode))
|
||||
var actionRes by mutableIntStateOf(actionButton.getDrawable())
|
||||
var showAltActionsDialog by mutableStateOf(false)
|
||||
var dlPercent by mutableIntStateOf(0)
|
||||
var inQueueState by mutableStateOf(curQueue.contains(episode))
|
||||
|
@ -150,7 +159,7 @@ class EpisodeVM(var episode: Episode) {
|
|||
withContext(Dispatchers.Main) {
|
||||
playedState = changes.obj.isPlayed()
|
||||
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")
|
||||
|
@ -174,7 +183,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")
|
||||
|
@ -185,6 +194,20 @@ class EpisodeVM(var episode: Episode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override fun equals(other: Any?): Boolean {
|
||||
// if (this === other) return true
|
||||
// if (javaClass != other?.javaClass) return false
|
||||
// other as EpisodeVM
|
||||
//
|
||||
// if (episode.id != other.episode.id) return false
|
||||
// return true
|
||||
// }
|
||||
//
|
||||
// override fun hashCode(): Int {
|
||||
// var result = episode.id.hashCode()
|
||||
// return result
|
||||
// }
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -193,10 +216,12 @@ fun ChooseRatingDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
Surface(shape = RoundedCornerShape(16.dp)) {
|
||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||
for (rating in Rating.entries.reversed()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
|
||||
for (item in selected) Episodes.setRating(item, rating.code)
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier
|
||||
.padding(4.dp)
|
||||
.clickable {
|
||||
for (item in selected) Episodes.setRating(item, rating.code)
|
||||
onDismissRequest()
|
||||
}) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = rating.res), "")
|
||||
Text(rating.name, Modifier.padding(start = 4.dp))
|
||||
}
|
||||
|
@ -264,7 +289,9 @@ fun ShelveDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
Dialog(onDismissRequest = onDismissRequest) {
|
||||
Surface(shape = RoundedCornerShape(16.dp)) {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(modifier = Modifier.verticalScroll(scrollState).padding(16.dp), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
Column(modifier = Modifier
|
||||
.verticalScroll(scrollState)
|
||||
.padding(16.dp), verticalArrangement = Arrangement.spacedBy(1.dp)) {
|
||||
var removeChecked by remember { mutableStateOf(false) }
|
||||
var toFeed by remember { mutableStateOf<Feed?>(null) }
|
||||
for (f in synthetics) {
|
||||
|
@ -317,7 +344,7 @@ fun ShelveDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
|||
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>, feed: Feed? = null,
|
||||
fun EpisodeLazyColumn(activity: MainActivity, vms: List<EpisodeVM>, feed: Feed? = null,
|
||||
refreshCB: (()->Unit)? = null, leftSwipeCB: ((Episode) -> Unit)? = null, rightSwipeCB: ((Episode) -> Unit)? = null,
|
||||
actionButton_: ((Episode)-> EpisodeActionButton)? = null) {
|
||||
val TAG = "EpisodeLazyColumn"
|
||||
|
@ -350,7 +377,6 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
val message = stringResource(R.string.erase_episodes_confirmation_msg)
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
var textState by remember { mutableStateOf(TextFieldValue("")) }
|
||||
val context = LocalContext.current
|
||||
|
||||
Dialog(onDismissRequest = onDismissRequest) {
|
||||
Surface(shape = RoundedCornerShape(16.dp)) {
|
||||
|
@ -359,7 +385,11 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
Text(stringResource(R.string.feed_delete_reason_msg))
|
||||
BasicTextField(value = textState, onValueChange = { textState = it },
|
||||
textStyle = TextStyle(fontSize = 16.sp, color = textColor),
|
||||
modifier = Modifier.fillMaxWidth().height(100.dp).padding(start = 10.dp, end = 10.dp, bottom = 10.dp).border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small)
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(100.dp)
|
||||
.padding(start = 10.dp, end = 10.dp, bottom = 10.dp)
|
||||
.border(1.dp, MaterialTheme.colorScheme.primary, MaterialTheme.shapes.small)
|
||||
)
|
||||
Button(onClick = {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
@ -403,7 +433,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
fun EpisodeSpeedDial(modifier: Modifier = Modifier) {
|
||||
var isExpanded by remember { mutableStateOf(false) }
|
||||
val options = mutableListOf<@Composable () -> Unit>(
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -412,19 +443,22 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media")
|
||||
Text(stringResource(id = R.string.delete_episode_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_download: ${selected.size}")
|
||||
for (episode in selected) {
|
||||
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()
|
||||
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface
|
||||
.get()
|
||||
?.download(activity, episode)
|
||||
}
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download")
|
||||
Text(stringResource(id = R.string.download_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -433,7 +467,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_mark_played), "Toggle played state")
|
||||
Text(stringResource(id = R.string.toggle_played_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -442,7 +477,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue")
|
||||
Text(stringResource(id = R.string.remove_from_queue_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -451,7 +487,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "Add to active queue")
|
||||
Text(stringResource(id = R.string.add_to_queue_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -460,7 +497,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.baseline_shelves_24), "Shelve")
|
||||
Text(stringResource(id = R.string.shelve_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -469,7 +507,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "Add to queue...")
|
||||
Text(stringResource(id = R.string.put_in_queue_label)) } },
|
||||
{ Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
{ Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
selectMode = false
|
||||
Logd(TAG, "ic_star: ${selected.size}")
|
||||
|
@ -481,7 +520,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
)
|
||||
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
|
||||
|
@ -507,7 +547,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}
|
||||
if (feed != null && feed.id <= MAX_SYNTHETIC_ID) {
|
||||
options.add {
|
||||
Row(modifier = Modifier.padding(horizontal = 16.dp)
|
||||
Row(modifier = Modifier
|
||||
.padding(horizontal = 16.dp)
|
||||
.clickable {
|
||||
isExpanded = false
|
||||
selectMode = false
|
||||
|
@ -523,7 +564,9 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
val scrollState = rememberScrollState()
|
||||
Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) {
|
||||
if (isExpanded) options.forEachIndexed { _, button ->
|
||||
FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp),
|
||||
FloatingActionButton(modifier = Modifier
|
||||
.padding(start = 4.dp, bottom = 6.dp)
|
||||
.height(40.dp),
|
||||
containerColor = Color.LightGray,
|
||||
onClick = {}) { button() }
|
||||
}
|
||||
|
@ -533,14 +576,146 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainRow(vm: EpisodeVM, index: Int) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
fun toggleSelected() {
|
||||
vm.isSelected = !vm.isSelected
|
||||
if (vm.isSelected) selected.add(vms[index].episode)
|
||||
else selected.remove(vms[index].episode)
|
||||
}
|
||||
Row(Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
|
||||
if (false) {
|
||||
val typedValue = TypedValue()
|
||||
LocalContext.current.theme.resolveAttribute(R.attr.dragview_background, typedValue, true)
|
||||
Icon(imageVector = ImageVector.vectorResource(typedValue.resourceId), tint = textColor, contentDescription = "drag handle",
|
||||
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically))
|
||||
}
|
||||
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")
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "imgvCover",
|
||||
modifier = Modifier.width(56.dp).height(56.dp)
|
||||
.constrainAs(imgvCover) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
start.linkTo(parent.start)
|
||||
}
|
||||
.clickable(onClick = {
|
||||
Logd(TAG, "icon clicked!")
|
||||
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",
|
||||
modifier = Modifier.background(Color.Green).alpha(alpha)
|
||||
.constrainAs(checkMark) {
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end)
|
||||
})
|
||||
}
|
||||
Column(Modifier.weight(1f).padding(start = 6.dp, end = 6.dp)
|
||||
.combinedClickable(onClick = {
|
||||
Logd(TAG, "clicked: ${vm.episode.title}")
|
||||
if (selectMode) toggleSelected()
|
||||
else activity.loadChildFragment(EpisodeInfoFragment.newInstance(vm.episode))
|
||||
}, onLongClick = {
|
||||
selectMode = !selectMode
|
||||
vm.isSelected = selectMode
|
||||
selected.clear()
|
||||
if (selectMode) {
|
||||
selected.add(vms[index].episode)
|
||||
longPressIndex = index
|
||||
} else {
|
||||
selectedSize = 0
|
||||
longPressIndex = -1
|
||||
}
|
||||
Logd(TAG, "long clicked: ${vm.episode.title}")
|
||||
})) {
|
||||
LaunchedEffect(key1 = queueChanged) {
|
||||
if (index >= vms.size) return@LaunchedEffect
|
||||
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))
|
||||
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))
|
||||
if (vm.inQueueState)
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", 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) }
|
||||
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " +
|
||||
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
|
||||
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Text(vm.episode.title ?: "", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
fun isDownloading(): Boolean {
|
||||
return vms[index].downloadState > DownloadStatus.State.UNKNOWN.ordinal && vms[index].downloadState < DownloadStatus.State.COMPLETED.ordinal
|
||||
}
|
||||
if (actionButton_ == null) {
|
||||
LaunchedEffect(key1 = status, key2 = vm.downloadState) {
|
||||
if (index >= vms.size) return@LaunchedEffect
|
||||
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media?.downloadUrl ?: "") ?: 0
|
||||
Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}")
|
||||
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
|
||||
vm.actionButton = forItem(vm.episode)
|
||||
// vm.actionRes = vm.actionButton!!.getDrawable()
|
||||
}
|
||||
} else {
|
||||
LaunchedEffect(vm.actionButton) {
|
||||
Logd(TAG, "LaunchedEffect init actionButton")
|
||||
vm.actionButton = actionButton_(vm.episode)
|
||||
// vm.actionRes = vm.actionButton!!.getDrawable()
|
||||
}
|
||||
}
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp).align(Alignment.CenterVertically)
|
||||
.pointerInput(Unit) {
|
||||
detectTapGestures(onLongPress = { vms[index].showAltActionsDialog = true },
|
||||
onTap = { vms[index].actionButton.onClick(activity) })
|
||||
}, ) {
|
||||
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 },
|
||||
strokeWidth = 4.dp, color = textColor, modifier = Modifier.width(30.dp).height(35.dp))
|
||||
}
|
||||
if (vm.showAltActionsDialog) vm.actionButton.AltActionsDialog(activity, vm.showAltActionsDialog,
|
||||
onDismiss = { vm.showAltActionsDialog = false })
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProgressRow(vm: EpisodeVM, index: Int) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
if (vm.inProgressState || InTheatre.isCurMedia(vm.episode.media)) {
|
||||
val pos = vm.positionState
|
||||
val dur = remember { vm.episode.media?.getDuration() ?: 0 }
|
||||
val durText = remember { DurationConverter.getDurationStringLong(dur) }
|
||||
vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
|
||||
Logd(TAG, "$index vm.prog: ${vm.prog}")
|
||||
Row {
|
||||
Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically))
|
||||
Text(durText, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var refreshing by remember { mutableStateOf(false)}
|
||||
PullToRefreshBox(modifier = Modifier.fillMaxWidth(), isRefreshing = refreshing, indicator = {}, onRefresh = {
|
||||
refreshing = true
|
||||
refreshCB?.invoke()
|
||||
refreshing = false
|
||||
}) {
|
||||
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
itemsIndexed(vms, key = {index, vm -> vm.episode.id}) { index, vm ->
|
||||
vm.startMonitoring()
|
||||
DisposableEffect(Unit) {
|
||||
|
@ -549,180 +724,48 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
vm.stopMonitoring()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(vm.actionButton) {
|
||||
Logd(TAG, "LaunchedEffect init actionButton")
|
||||
if (vm.actionButton == null) {
|
||||
vm.actionButton = if (actionButton_ != null) actionButton_(vm.episode) else EpisodeActionButton.forItem(vm.episode)
|
||||
vm.actionRes = vm.actionButton!!.getDrawable()
|
||||
}
|
||||
}
|
||||
val velocityTracker = remember { VelocityTracker() }
|
||||
val offsetX = remember { Animatable(0f) }
|
||||
Box(modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
|
||||
detectHorizontalDragGestures(onDragStart = { velocityTracker.resetTracking() },
|
||||
onHorizontalDrag = { change, dragAmount ->
|
||||
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
||||
coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) }
|
||||
},
|
||||
onDragEnd = {
|
||||
coroutineScope.launch {
|
||||
val velocity = velocityTracker.calculateVelocity().x
|
||||
if (velocity > 1000f || velocity < -1000f) {
|
||||
Logd(TAG, "velocity: $velocity")
|
||||
Logd(TAG, "top box")
|
||||
detectHorizontalDragGestures(onDragStart = { velocityTracker.resetTracking() },
|
||||
onHorizontalDrag = { change, dragAmount ->
|
||||
Logd(TAG, "onHorizontalDrag $dragAmount")
|
||||
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
||||
coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) }
|
||||
},
|
||||
onDragEnd = {
|
||||
coroutineScope.launch {
|
||||
val velocity = velocityTracker.calculateVelocity().x
|
||||
Logd(TAG, "velocity: $velocity")
|
||||
if (velocity > 1000f || velocity < -1000f) {
|
||||
// Logd(TAG, "velocity: $velocity")
|
||||
// if (velocity > 0) rightSwipeCB?.invoke(vms[index].episode)
|
||||
// else leftSwipeCB?.invoke(vms[index].episode)
|
||||
if (velocity > 0) rightSwipeCB?.invoke(vm.episode)
|
||||
else leftSwipeCB?.invoke(vm.episode)
|
||||
}
|
||||
offsetX.animateTo(targetValue = 0f, animationSpec = tween(500))
|
||||
if (velocity > 0) rightSwipeCB?.invoke(vm.episode)
|
||||
else leftSwipeCB?.invoke(vm.episode)
|
||||
}
|
||||
offsetX.animateTo(targetValue = 0f, animationSpec = tween(500))
|
||||
}
|
||||
)
|
||||
}.offset { IntOffset(offsetX.value.roundToInt(), 0) }
|
||||
) {
|
||||
}
|
||||
)
|
||||
}.offset { IntOffset(offsetX.value.roundToInt(), 0) }) {
|
||||
LaunchedEffect(key1 = selectMode, key2 = selectedSize) {
|
||||
vm.isSelected = selectMode && vm.episode in selected
|
||||
// Logd(TAG, "LaunchedEffect $index $isSelected ${selected.size}")
|
||||
Logd(TAG, "LaunchedEffect $index ${vm.isSelected} ${selected.size}")
|
||||
}
|
||||
fun toggleSelected() {
|
||||
vm.isSelected = !vm.isSelected
|
||||
if (vm.isSelected) selected.add(vms[index].episode)
|
||||
else selected.remove(vms[index].episode)
|
||||
}
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Column {
|
||||
val dur = remember { vm.episode.media?.getDuration() ?: 0 }
|
||||
val durText = remember { DurationConverter.getDurationStringLong(dur) }
|
||||
Row(Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
|
||||
if (false) {
|
||||
val typedValue = TypedValue()
|
||||
LocalContext.current.theme.resolveAttribute(R.attr.dragview_background, typedValue, true)
|
||||
Icon(painter = painterResource(typedValue.resourceId), tint = textColor, contentDescription = "drag handle",
|
||||
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically))
|
||||
}
|
||||
Logd(TAG, "episode.imageUrl: ${vm.episode.imageUrl}")
|
||||
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
|
||||
val (imgvCover, checkMark) = createRefs()
|
||||
val imgLoc = remember { ImageResourceUtils.getEpisodeListImageLocation(vm.episode) }
|
||||
Logd(TAG, "imgLoc: $imgLoc")
|
||||
val painter = rememberAsyncImagePainter(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED)
|
||||
.placeholder(R.mipmap.ic_launcher)
|
||||
.error(R.mipmap.ic_launcher).build())
|
||||
Image(painter = painter, contentDescription = "imgvCover",
|
||||
modifier = Modifier.width(56.dp).height(56.dp)
|
||||
.constrainAs(imgvCover) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
start.linkTo(parent.start)
|
||||
}.clickable(onClick = {
|
||||
Logd(TAG, "icon clicked!")
|
||||
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(painter = painterResource(R.drawable.ic_check), tint = textColor, contentDescription = "played_mark",
|
||||
modifier = Modifier.background(Color.Green).alpha(alpha).constrainAs(checkMark) {
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end)
|
||||
})
|
||||
}
|
||||
Column(Modifier.weight(1f).padding(start = 6.dp, end = 6.dp)
|
||||
.combinedClickable(onClick = {
|
||||
Logd(TAG, "clicked: ${vm.episode.title}")
|
||||
if (selectMode) toggleSelected()
|
||||
else activity.loadChildFragment(EpisodeInfoFragment.newInstance(vm.episode))
|
||||
}, onLongClick = {
|
||||
selectMode = !selectMode
|
||||
vm.isSelected = selectMode
|
||||
selected.clear()
|
||||
if (selectMode) {
|
||||
selected.add(vms[index].episode)
|
||||
longPressIndex = index
|
||||
} else {
|
||||
selectedSize = 0
|
||||
longPressIndex = -1
|
||||
}
|
||||
Logd(TAG, "long clicked: ${vm.episode.title}")
|
||||
})) {
|
||||
LaunchedEffect(key1 = queueChanged) {
|
||||
if (index >= vms.size) return@LaunchedEffect
|
||||
vms[index].inQueueState = curQueue.contains(vms[index].episode)
|
||||
}
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
|
||||
Icon(painter = painterResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo",
|
||||
modifier = Modifier.width(14.dp).height(14.dp))
|
||||
val ratingIconRes = Rating.fromCode(vm.ratingState).res
|
||||
if (vm.ratingState != Rating.UNRATED.code)
|
||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(14.dp).height(14.dp))
|
||||
if (vm.inQueueState)
|
||||
Icon(painter = painterResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist",
|
||||
modifier = Modifier.width(14.dp).height(14.dp))
|
||||
val curContext = LocalContext.current
|
||||
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " +
|
||||
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
|
||||
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
}
|
||||
Text(vm.episode.title ?: "", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
fun isDownloading(): Boolean {
|
||||
return vms[index].downloadState > DownloadStatus.State.UNKNOWN.ordinal && vms[index].downloadState < DownloadStatus.State.COMPLETED.ordinal
|
||||
}
|
||||
if (actionButton_ == null) {
|
||||
LaunchedEffect(vms[index].downloadState) {
|
||||
if (index >= vms.size) return@LaunchedEffect
|
||||
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media?.downloadUrl ?: "") ?: 0
|
||||
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
|
||||
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
||||
vm.actionRes = vm.actionButton!!.getDrawable()
|
||||
}
|
||||
LaunchedEffect(key1 = status) {
|
||||
if (index >= vms.size) return@LaunchedEffect
|
||||
Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}")
|
||||
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
||||
Logd(TAG, "LaunchedEffect vm.actionButton: ${vm.actionButton?.getLabel()}")
|
||||
vm.actionRes = vm.actionButton!!.getDrawable()
|
||||
}
|
||||
// LaunchedEffect(vm.isPlayingState) {
|
||||
// Logd(TAG, "LaunchedEffect isPlayingState: $index ${vms[index].isPlayingState} ${vm.isPlayingState}")
|
||||
// vms[index].actionButton = EpisodeActionButton.forItem(vms[index].episode)
|
||||
// vms[index].actionRes = vm.actionButton.getDrawable()
|
||||
// }
|
||||
}
|
||||
Box(modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp)
|
||||
.align(Alignment.CenterVertically).pointerInput(Unit) {
|
||||
detectTapGestures(onLongPress = { vm.showAltActionsDialog = true }, onTap = {
|
||||
vms[index].actionButton?.onClick(activity)
|
||||
})
|
||||
}, contentAlignment = Alignment.Center) {
|
||||
// actionRes = actionButton.getDrawable()
|
||||
Icon(painter = painterResource(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 },
|
||||
strokeWidth = 4.dp, color = textColor, modifier = Modifier.width(30.dp).height(35.dp))
|
||||
}
|
||||
if (vm.showAltActionsDialog) vm.actionButton?.AltActionsDialog(activity, vm.showAltActionsDialog,
|
||||
onDismiss = { vm.showAltActionsDialog = false })
|
||||
}
|
||||
if (vm.inProgressState || InTheatre.isCurMedia(vm.episode.media)) {
|
||||
val pos = vm.positionState
|
||||
vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
|
||||
Logd(TAG, "$index vm.prog: ${vm.prog}")
|
||||
Row {
|
||||
Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically))
|
||||
Text(durText, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
}
|
||||
MainRow(vm, index)
|
||||
ProgressRow(vm, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (selectMode) {
|
||||
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp).background(Color.LightGray), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp)
|
||||
.background(Color.LightGray), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
.clickable(onClick = {
|
||||
selected.clear()
|
||||
for (i in 0..longPressIndex) {
|
||||
|
@ -731,7 +774,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
selectedSize = selected.size
|
||||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
.clickable(onClick = {
|
||||
selected.clear()
|
||||
for (i in longPressIndex..<vms.size) {
|
||||
|
@ -741,19 +785,19 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
|||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) }
|
||||
Icon(painter = painterResource(selectAllRes), tint = Color.Black, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
Icon(imageVector = ImageVector.vectorResource(selectAllRes), tint = Color.Black, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp)
|
||||
.clickable(onClick = {
|
||||
if (selectedSize != vms.size) {
|
||||
selected.clear()
|
||||
for (vm in vms) {
|
||||
selected.add(vm.episode)
|
||||
}
|
||||
selectAllRes = R.drawable.ic_select_none
|
||||
} else {
|
||||
selected.clear()
|
||||
longPressIndex = -1
|
||||
selectAllRes = R.drawable.ic_select_all
|
||||
}
|
||||
if (selectedSize != vms.size) {
|
||||
selected.clear()
|
||||
for (vm in vms) {
|
||||
selected.add(vm.episode)
|
||||
}
|
||||
selectAllRes = R.drawable.ic_select_none
|
||||
} else {
|
||||
selected.clear()
|
||||
longPressIndex = -1
|
||||
selectAllRes = R.drawable.ic_select_all
|
||||
}
|
||||
selectedSize = selected.size
|
||||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
|
@ -790,7 +834,10 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
|
|||
val info = StreamInfo.getInfo(Vista.getService(0), url)
|
||||
val episode = episodeFromStreamInfo(info)
|
||||
val status = addToYoutubeSyndicate(episode, !audioOnly)
|
||||
if (log != null) upsert(log) { it.status = status }
|
||||
if (log != null) upsert(log) {
|
||||
it.title = episode.title
|
||||
it.status = status
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
toastMassege = "Receive share error: ${e.message}"
|
||||
Log.e(TAG, toastMassege)
|
||||
|
@ -803,7 +850,8 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
|
|||
}) {
|
||||
Text("Confirm")
|
||||
}
|
||||
} else CircularProgressIndicator(progress = { 0.6f }, strokeWidth = 4.dp, modifier = Modifier.padding(start = 20.dp, end = 20.dp).width(30.dp).height(30.dp))
|
||||
} else CircularProgressIndicator(progress = { 0.6f }, strokeWidth = 4.dp,
|
||||
modifier = Modifier.padding(start = 20.dp, end = 20.dp).width(30.dp).height(30.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ import androidx.compose.ui.draw.alpha
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
|
@ -41,6 +40,8 @@ import androidx.compose.ui.unit.sp
|
|||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -160,6 +161,7 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
|
|||
showSubscribeDialog.value = false
|
||||
})
|
||||
}
|
||||
val context = LocalContext.current
|
||||
Column(Modifier.padding(start = 10.dp, end = 10.dp, top = 4.dp, bottom = 4.dp).combinedClickable(
|
||||
onClick = {
|
||||
if (feed.feedUrl != null) {
|
||||
|
@ -182,8 +184,9 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
|
|||
Row {
|
||||
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
|
||||
val (imgvCover, checkMark) = createRefs()
|
||||
AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover",
|
||||
placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
val imgLoc = remember(feed) { feed.imageUrl }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(), contentDescription = "imgvCover",
|
||||
modifier = Modifier.width(65.dp).height(65.dp).constrainAs(imgvCover) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
|
@ -194,7 +197,7 @@ fun OnlineFeedItem(activity: MainActivity, feed: PodcastSearchResult, log: Subsc
|
|||
Logd("OnlineFeedItem", "${feed.feedId} $log")
|
||||
val alpha = 1.0f
|
||||
val iRes = if (feed.feedId > 0) R.drawable.ic_check else R.drawable.baseline_clear_24
|
||||
Icon(painter = painterResource(iRes), tint = textColor, contentDescription = "played_mark",
|
||||
Icon(imageVector = ImageVector.vectorResource(iRes), tint = textColor, contentDescription = "played_mark",
|
||||
modifier = Modifier.background(Color.Green).alpha(alpha).constrainAs(checkMark) {
|
||||
bottom.linkTo(parent.bottom)
|
||||
end.linkTo(parent.end)
|
||||
|
|
|
@ -20,7 +20,6 @@ class CustomFeedNameDialog(activity: Activity, private var feed: Feed) {
|
|||
|
||||
fun show() {
|
||||
val activity = activityRef.get() ?: return
|
||||
|
||||
val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity))
|
||||
val title = feed.title
|
||||
|
||||
|
|
|
@ -63,8 +63,11 @@ import androidx.compose.material3.Text
|
|||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -78,6 +81,8 @@ import androidx.lifecycle.lifecycleScope
|
|||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.session.MediaController
|
||||
import coil.compose.AsyncImage
|
||||
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
|
||||
|
@ -108,6 +113,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
private var showTimeLeft = false
|
||||
private var titleText by mutableStateOf("")
|
||||
private var imgLoc by mutableStateOf<String?>(null)
|
||||
private var imgLocLarge by mutableStateOf<String?>(null)
|
||||
private var txtvPlaybackSpeed by mutableStateOf("")
|
||||
private var remainingTime by mutableIntStateOf(0)
|
||||
private var isVideoScreen = false
|
||||
|
@ -173,141 +179,152 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ControlUI() {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
val context = LocalContext.current
|
||||
Row {
|
||||
fun ensureService() {
|
||||
if (curMedia == null) return
|
||||
if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start()
|
||||
}
|
||||
val imgLoc_ = remember(currentItem) { imgLoc }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc_)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "imgvCover",
|
||||
modifier = Modifier.width(65.dp).height(65.dp).padding(start = 5.dp)
|
||||
.clickable(onClick = {
|
||||
Logd(TAG, "playerUiFragment icon was clicked")
|
||||
if (isCollapsed) {
|
||||
val media = curMedia
|
||||
if (media != null) {
|
||||
val mediaType = media.getMediaType()
|
||||
if (mediaType == MediaType.AUDIO || videoPlayMode == VideoMode.AUDIO_ONLY.code || videoMode == VideoMode.AUDIO_ONLY
|
||||
|| (media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy == VideoMode.AUDIO_ONLY)) {
|
||||
Logd(TAG, "popping as audio episode")
|
||||
ensureService()
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||
} else {
|
||||
Logd(TAG, "popping video activity")
|
||||
val intent = getPlayerActivityIntent(requireContext(), mediaType)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
} else (activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
}))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor,
|
||||
contentDescription = "speed",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
|
||||
VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
|
||||
}))
|
||||
Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_rewind), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && playbackService?.isServiceReady() == true)
|
||||
playbackService?.mPlayer?.seekDelta(-UserPreferences.rewindSecs * 1000)
|
||||
}, onLongClick = {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND)
|
||||
}))
|
||||
val rewindSecs = remember { NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()) }
|
||||
Text(rewindSecs, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(imageVector = ImageVector.vectorResource(playButRes), tint = textColor, contentDescription = "play",
|
||||
modifier = Modifier.width(64.dp).height(64.dp).combinedClickable(onClick = {
|
||||
if (controller == null) return@combinedClickable
|
||||
if (curMedia != null) {
|
||||
val media = curMedia!!
|
||||
setIsShowPlay(!isShowPlay)
|
||||
if (media.getMediaType() == MediaType.VIDEO && status != PlayerStatus.PLAYING &&
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
|
||||
playPause()
|
||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
|
||||
} else playPause()
|
||||
}
|
||||
}, onLongClick = {
|
||||
if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
||||
if (fallbackSpeed > 0.1f) toggleFallbackSpeed(fallbackSpeed)
|
||||
}
|
||||
}))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_forward), tint = textColor,
|
||||
contentDescription = "forward",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && playbackService?.isServiceReady() == true)
|
||||
playbackService?.mPlayer?.seekDelta(UserPreferences.fastForwardSecs * 1000)
|
||||
}, onLongClick = {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD)
|
||||
}))
|
||||
val fastForwardSecs = remember { NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()) }
|
||||
Text(fastForwardSecs, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
fun speedForward(speed: Float) {
|
||||
if (playbackService?.mPlayer == null || playbackService?.isFallbackSpeed == true) return
|
||||
if (playbackService?.isSpeedForward == false) {
|
||||
playbackService?.normalSpeed = playbackService?.mPlayer!!.getPlaybackSpeed()
|
||||
playbackService?.mPlayer!!.setPlaybackParams(speed, isSkipSilence)
|
||||
} else playbackService?.mPlayer?.setPlaybackParams(playbackService!!.normalSpeed, isSkipSilence)
|
||||
playbackService!!.isSpeedForward = !playbackService!!.isSpeedForward
|
||||
}
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_skip_48dp), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
val speedForward = UserPreferences.speedforwardSpeed
|
||||
if (speedForward > 0.1f) speedForward(speedForward)
|
||||
}
|
||||
}, onLongClick = {
|
||||
activity?.sendBroadcast(MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||
}))
|
||||
if (UserPreferences.speedforwardSpeed > 0.1f) Text(NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ProgressBar() {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Slider(value = sliderValue, valueRange = 0f..duration.toFloat(),
|
||||
modifier = Modifier.height(12.dp).padding(top = 2.dp, bottom = 2.dp),
|
||||
onValueChange = {
|
||||
Logd(TAG, "Slider onValueChange: $it")
|
||||
sliderValue = it
|
||||
}, onValueChangeFinished = {
|
||||
Logd(TAG, "Slider onValueChangeFinished: $sliderValue")
|
||||
currentPosition = sliderValue.toInt()
|
||||
if (playbackService?.isServiceReady() == true) seekTo(currentPosition)
|
||||
})
|
||||
Row {
|
||||
Text(DurationConverter.getDurationStringLong(currentPosition), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
Spacer(Modifier.weight(1f))
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
Text(txtvLengtTexth, color = textColor, style = MaterialTheme.typography.bodySmall, modifier = Modifier.clickable {
|
||||
if (controller == null) return@clickable
|
||||
showTimeLeft = !showTimeLeft
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
onPositionUpdate(FlowEvent.PlaybackPositionEvent(curMedia, curPositionFB, curDurationFB))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PlayerUI(modifier: Modifier) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Column(modifier = modifier.fillMaxWidth().background(MaterialTheme.colorScheme.surface)) {
|
||||
Text(titleText, maxLines = 1, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
Slider(value = sliderValue, valueRange = 0f..duration.toFloat(),
|
||||
// colors = SliderDefaults.colors(
|
||||
// thumbColor = MaterialTheme.colorScheme.secondary,
|
||||
// activeTrackColor = MaterialTheme.colorScheme.secondary,
|
||||
// inactiveTrackColor = Color.Gray,
|
||||
// ),
|
||||
modifier = Modifier.height(12.dp).padding(top = 2.dp, bottom = 2.dp),
|
||||
onValueChange = {
|
||||
Logd(TAG, "Slider onValueChange: $it")
|
||||
sliderValue = it
|
||||
}, onValueChangeFinished = {
|
||||
Logd(TAG, "Slider onValueChangeFinished: $sliderValue")
|
||||
currentPosition = sliderValue.toInt()
|
||||
if (playbackService?.isServiceReady() == true) seekTo(currentPosition)
|
||||
})
|
||||
Row {
|
||||
Text(DurationConverter.getDurationStringLong(currentPosition), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
Spacer(Modifier.weight(1f))
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
Text(txtvLengtTexth, color = textColor, style = MaterialTheme.typography.bodySmall, modifier = Modifier.clickable {
|
||||
if (controller == null) return@clickable
|
||||
showTimeLeft = !showTimeLeft
|
||||
UserPreferences.setShowRemainTimeSetting(showTimeLeft)
|
||||
onPositionUpdate(FlowEvent.PlaybackPositionEvent(curMedia, curPositionFB, curDurationFB))
|
||||
})
|
||||
}
|
||||
Row {
|
||||
fun ensureService() {
|
||||
if (curMedia == null) return
|
||||
if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start()
|
||||
}
|
||||
AsyncImage(model = imgLoc, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
modifier = Modifier.width(65.dp).height(65.dp).padding(start = 5.dp)
|
||||
.clickable(onClick = {
|
||||
Logd(TAG, "playerUiFragment icon was clicked")
|
||||
if (isCollapsed) {
|
||||
val media = curMedia
|
||||
if (media != null) {
|
||||
val mediaType = media.getMediaType()
|
||||
if (mediaType == MediaType.AUDIO || videoPlayMode == VideoMode.AUDIO_ONLY.code || videoMode == VideoMode.AUDIO_ONLY
|
||||
|| (media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy == VideoMode.AUDIO_ONLY)) {
|
||||
Logd(TAG, "popping as audio episode")
|
||||
ensureService()
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
|
||||
} else {
|
||||
Logd(TAG, "popping video activity")
|
||||
val intent = getPlayerActivityIntent(requireContext(), mediaType)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
} else (activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
}))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(painter = painterResource(R.drawable.ic_playback_speed), tint = textColor,
|
||||
contentDescription = "speed",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
|
||||
VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
|
||||
}))
|
||||
Text(txtvPlaybackSpeed, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(painter = painterResource(R.drawable.ic_fast_rewind), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && playbackService?.isServiceReady() == true) {
|
||||
playbackService?.mPlayer?.seekDelta(-UserPreferences.rewindSecs * 1000)
|
||||
}
|
||||
}, onLongClick = {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND)
|
||||
}))
|
||||
Text(NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(painter = painterResource(playButRes), tint = textColor, contentDescription = "play",
|
||||
modifier = Modifier.width(64.dp).height(64.dp).combinedClickable(onClick = {
|
||||
if (controller == null) return@combinedClickable
|
||||
if (curMedia != null) {
|
||||
val media = curMedia!!
|
||||
setIsShowPlay(!isShowPlay)
|
||||
if (media.getMediaType() == MediaType.VIDEO && status != PlayerStatus.PLAYING &&
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
|
||||
playPause()
|
||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
|
||||
} else playPause()
|
||||
}
|
||||
}, onLongClick = {
|
||||
if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
||||
if (fallbackSpeed > 0.1f) toggleFallbackSpeed(fallbackSpeed)
|
||||
}
|
||||
}))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(painter = painterResource(R.drawable.ic_fast_forward), tint = textColor,
|
||||
contentDescription = "forward",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && playbackService?.isServiceReady() == true) {
|
||||
playbackService?.mPlayer?.seekDelta(UserPreferences.fastForwardSecs * 1000)
|
||||
}
|
||||
}, onLongClick = {
|
||||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD)
|
||||
}))
|
||||
Text(NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
fun speedForward(speed: Float) {
|
||||
if (playbackService?.mPlayer == null || playbackService?.isFallbackSpeed == true) return
|
||||
if (playbackService?.isSpeedForward == false) {
|
||||
playbackService?.normalSpeed = playbackService?.mPlayer!!.getPlaybackSpeed()
|
||||
playbackService?.mPlayer!!.setPlaybackParams(speed, isSkipSilence)
|
||||
} else playbackService?.mPlayer?.setPlaybackParams(playbackService!!.normalSpeed, isSkipSilence)
|
||||
playbackService!!.isSpeedForward = !playbackService!!.isSpeedForward
|
||||
}
|
||||
Icon(painter = painterResource(R.drawable.ic_skip_48dp), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
val speedForward = UserPreferences.speedforwardSpeed
|
||||
if (speedForward > 0.1f) speedForward(speedForward)
|
||||
}
|
||||
}, onLongClick = {
|
||||
activity?.sendBroadcast(MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||
}))
|
||||
if (UserPreferences.speedforwardSpeed > 0.1f) Text(NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
}
|
||||
ProgressBar()
|
||||
ControlUI()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,19 +336,17 @@ class AudioPlayerFragment : Fragment() {
|
|||
val mediaType = curMedia?.getMediaType()
|
||||
val notAudioOnly = (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY
|
||||
Row(modifier = Modifier.fillMaxWidth().padding(10.dp), horizontalArrangement = Arrangement.SpaceBetween) {
|
||||
Icon(painter = painterResource(R.drawable.ic_arrow_down), tint = textColor, contentDescription = "Collapse", modifier = Modifier.clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_arrow_down), tint = textColor, contentDescription = "Collapse", modifier = Modifier.clickable {
|
||||
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
|
||||
})
|
||||
var homeIcon by remember { mutableIntStateOf(R.drawable.baseline_home_24)}
|
||||
Icon(painter = painterResource(homeIcon), tint = textColor, contentDescription = "Home", modifier = Modifier.clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(homeIcon), tint = textColor, contentDescription = "Home", modifier = Modifier.clickable {
|
||||
homeIcon = if (showHomeText) R.drawable.ic_home else R.drawable.outline_home_24
|
||||
buildHomeReaderText()
|
||||
})
|
||||
if (mediaType == MediaType.VIDEO) Icon(painter = painterResource(R.drawable.baseline_fullscreen_24), tint = textColor, contentDescription = "Play video",
|
||||
if (mediaType == MediaType.VIDEO) Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_fullscreen_24), tint = textColor, contentDescription = "Play video",
|
||||
modifier = Modifier.clickable {
|
||||
if (notAudioOnly || (curMedia as? EpisodeMedia)?.forceVideo == true) {
|
||||
// playPause()
|
||||
} else {
|
||||
if (!notAudioOnly && (curMedia as? EpisodeMedia)?.forceVideo != true) {
|
||||
(curMedia as? EpisodeMedia)?.forceVideo = true
|
||||
status = PlayerStatus.STOPPED
|
||||
playbackService?.mPlayer?.pause(true, reinit = true)
|
||||
|
@ -341,24 +356,24 @@ class AudioPlayerFragment : Fragment() {
|
|||
})
|
||||
if (controller != null) {
|
||||
val sleepRes = if (sleepTimerActive) R.drawable.ic_sleep_off else R.drawable.ic_sleep
|
||||
Icon(painter = painterResource(sleepRes), tint = textColor, contentDescription = "Sleep timer", modifier = Modifier.clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(sleepRes), tint = textColor, contentDescription = "Sleep timer", modifier = Modifier.clickable {
|
||||
SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
|
||||
})
|
||||
}
|
||||
if (currentMedia is EpisodeMedia) Icon(painter = painterResource(R.drawable.ic_feed), tint = textColor, contentDescription = "Open podcast",
|
||||
if (currentMedia is EpisodeMedia) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_feed), tint = textColor, contentDescription = "Open podcast",
|
||||
modifier = Modifier.clickable {
|
||||
if (feedItem?.feedId != null) {
|
||||
val intent: Intent = MainActivity.getIntentToOpenFeed(requireContext(), feedItem.feedId!!)
|
||||
startActivity(intent)
|
||||
}
|
||||
})
|
||||
Icon(painter = painterResource(R.drawable.ic_share), tint = textColor, contentDescription = "Share", modifier = Modifier.clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_share), tint = textColor, contentDescription = "Share", modifier = Modifier.clickable {
|
||||
if (currentItem != null) {
|
||||
val shareDialog: ShareDialog = ShareDialog.newInstance(currentItem!!)
|
||||
shareDialog.show((requireActivity().supportFragmentManager), "ShareEpisodeDialog")
|
||||
}
|
||||
})
|
||||
Icon(painter = painterResource(R.drawable.baseline_offline_share_24), tint = textColor, contentDescription = "Share Note", modifier = Modifier.clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_offline_share_24), tint = textColor, contentDescription = "Share Note", modifier = Modifier.clickable {
|
||||
val notes = if (showHomeText) readerhtml else feedItem?.description
|
||||
if (!notes.isNullOrEmpty()) {
|
||||
val shareText = HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
|
@ -407,7 +422,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
Row(modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 2.dp)) {
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
val ratingIconRes = Rating.fromCode(rating).res
|
||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = {
|
||||
showChooseRatingDialog = true
|
||||
}))
|
||||
|
@ -453,7 +468,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
if (displayedChapterIndex >= 0) {
|
||||
Row(modifier = Modifier.padding(start = 20.dp, end = 20.dp),
|
||||
horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(painter = painterResource(R.drawable.ic_chapter_prev), tint = textColor, contentDescription = "prev_chapter",
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_chapter_prev), tint = textColor, contentDescription = "prev_chapter",
|
||||
modifier = Modifier.width(36.dp).height(36.dp).clickable(onClick = { seekToPrevChapter() }))
|
||||
Text("Ch " + displayedChapterIndex.toString() + ": " + currentChapter?.title,
|
||||
color = textColor, style = MaterialTheme.typography.bodyMedium,
|
||||
|
@ -461,11 +476,11 @@ class AudioPlayerFragment : Fragment() {
|
|||
modifier = Modifier.weight(1f).padding(start = 10.dp, end = 10.dp)
|
||||
// .clickable(onClick = { ChaptersFragment().show(childFragmentManager, ChaptersFragment.TAG) }))
|
||||
.clickable(onClick = { showChaptersDialog = true }))
|
||||
if (hasNextChapter) Icon(painter = painterResource(R.drawable.ic_chapter_next), tint = textColor, contentDescription = "next_chapter",
|
||||
if (hasNextChapter) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_chapter_next), tint = textColor, contentDescription = "next_chapter",
|
||||
modifier = Modifier.width(36.dp).height(36.dp).clickable(onClick = { seekToNextChapter() }))
|
||||
}
|
||||
}
|
||||
AsyncImage(model = imgLoc, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
AsyncImage(model = imgLocLarge, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 32.dp, end = 32.dp, top = 10.dp).clickable(onClick = {
|
||||
}))
|
||||
}
|
||||
|
@ -489,6 +504,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
@UnstableApi
|
||||
fun onPositionUpdate(event: FlowEvent.PlaybackPositionEvent) {
|
||||
Logd(TAG, "onPositionUpdate")
|
||||
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)
|
||||
|
@ -499,11 +515,9 @@ class AudioPlayerFragment : Fragment() {
|
|||
return
|
||||
}
|
||||
showTimeLeft = UserPreferences.shouldShowRemainingTime()
|
||||
txtvLengtTexth = if (showTimeLeft) {
|
||||
(if (remainingTime > 0) "-" else "") + DurationConverter.getDurationStringLong(remainingTime)
|
||||
} else DurationConverter.getDurationStringLong(duration)
|
||||
txtvLengtTexth = if (showTimeLeft) (if (remainingTime > 0) "-" else "") + DurationConverter.getDurationStringLong(remainingTime)
|
||||
else DurationConverter.getDurationStringLong(duration)
|
||||
|
||||
// val progress: Float = (event.position.toFloat()) / event.duration
|
||||
sliderValue = event.position.toFloat()
|
||||
}
|
||||
private fun onPlaybackServiceChanged(event: FlowEvent.PlaybackServiceEvent) {
|
||||
|
@ -645,7 +659,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
|
||||
private fun displayCoverImage() {
|
||||
if (currentMedia == null) return
|
||||
imgLoc = if (displayedChapterIndex == -1 || currentMedia!!.getChapters().isEmpty() || currentMedia!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty())
|
||||
imgLocLarge = if (displayedChapterIndex == -1 || currentMedia!!.getChapters().isEmpty() || currentMedia!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty())
|
||||
currentMedia!!.getImageLocation() else EmbeddedChapterImage.getModelFor(currentMedia!!, displayedChapterIndex)?.toString()
|
||||
Logd(TAG, "displayCoverImage: imgLoc: $imgLoc")
|
||||
}
|
||||
|
|
|
@ -71,8 +71,10 @@ import androidx.compose.material3.Text
|
|||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -199,7 +201,7 @@ 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
|
||||
Icon(painter = painterResource(playedIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "isPlayed",
|
||||
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) {
|
||||
|
@ -228,7 +230,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
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(painter = painterResource(inQueueIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "inQueue",
|
||||
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!!)
|
||||
}))
|
||||
|
@ -236,12 +238,12 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
Logd(TAG, "ratingIconRes rating: $rating")
|
||||
val ratingIconRes = Rating.fromCode(rating).res
|
||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
Icon(imageVector = ImageVector.vectorResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = {
|
||||
showChooseRatingDialog = true
|
||||
}))
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
if (hasMedia) Icon(painter = painterResource(actionButton1?.getDrawable()?: R.drawable.ic_questionmark), tint = textColor, contentDescription = "butAction1",
|
||||
if (hasMedia) Icon(imageVector = ImageVector.vectorResource(actionButton1?.getDrawable()?: R.drawable.ic_questionmark), tint = textColor, contentDescription = "butAction1",
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = {
|
||||
when {
|
||||
actionButton1 is StreamActionButton && !UserPreferences.isStreamOverDownload
|
||||
|
@ -254,7 +256,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}))
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
Icon(painter = painterResource(R.drawable.baseline_home_work_24), tint = textColor, contentDescription = "homeButton",
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_home_work_24), tint = textColor, contentDescription = "homeButton",
|
||||
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = {
|
||||
if (!episode?.link.isNullOrEmpty()) {
|
||||
homeFragment = EpisodeHomeFragment.newInstance(episode!!)
|
||||
|
@ -263,7 +265,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}))
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
Box(modifier = Modifier.width(40.dp).height(40.dp).align(Alignment.CenterVertically), contentAlignment = Alignment.Center) {
|
||||
Icon(painter = painterResource(actionButton2?.getDrawable()?: R.drawable.ic_questionmark), tint = textColor, contentDescription = "butAction2", modifier = Modifier.width(24.dp).height(24.dp).clickable {
|
||||
Icon(imageVector = ImageVector.vectorResource(actionButton2?.getDrawable()?: R.drawable.ic_questionmark), tint = textColor, contentDescription = "butAction2", modifier = Modifier.width(24.dp).height(24.dp).clickable {
|
||||
when {
|
||||
actionButton2 is DownloadActionButton && UserPreferences.isStreamOverDownload
|
||||
&& UsageStatistics.hasSignificantBiasTo(UsageStatistics.ACTION_DOWNLOAD) -> {
|
||||
|
|
|
@ -46,8 +46,10 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -249,10 +251,10 @@ import java.util.concurrent.Semaphore
|
|||
start.linkTo(parent.start)
|
||||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Spacer(modifier = Modifier.weight(0.7f))
|
||||
Icon(painter = painterResource(R.drawable.ic_filter_white), tint = if (filterButColor == Color.White) textColor else filterButColor, contentDescription = "butFilter",
|
||||
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(painter = painterResource(R.drawable.ic_settings_white), tint = textColor, contentDescription = "butShowSettings",
|
||||
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)
|
||||
|
@ -262,12 +264,12 @@ import java.util.concurrent.Semaphore
|
|||
Spacer(modifier = Modifier.weight(0.5f))
|
||||
Text(episodes.size.toString() + " / " + feed?.episodes?.size?.toString(), textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
// Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
||||
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
||||
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
|
||||
// bottom.linkTo(parent.bottom)
|
||||
// start.linkTo(parent.start)
|
||||
// })
|
||||
// Image(painter = painterResource(R.drawable.ic_rounded_corner_right), contentDescription = "right_corner",
|
||||
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_right), contentDescription = "right_corner",
|
||||
// Modifier.width(12.dp).height(12.dp).constrainAs(image2) {
|
||||
// bottom.linkTo(parent.bottom)
|
||||
// end.linkTo(parent.end)
|
||||
|
|
|
@ -52,9 +52,11 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
@ -173,12 +175,12 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}, verticalAlignment = Alignment.CenterVertically) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
val ratingIconRes = Rating.fromCode(rating).res
|
||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
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(painter = painterResource(R.drawable.ic_settings_white), tint = textColor, contentDescription = "butShowSettings",
|
||||
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 = {
|
||||
(activity as MainActivity).loadChildFragment(FeedSettingsFragment.newInstance(feed), TransitionEffect.SLIDE)
|
||||
}))
|
||||
|
@ -188,12 +190,12 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
Spacer(modifier = Modifier.width(15.dp))
|
||||
}
|
||||
// Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
||||
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
||||
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
|
||||
// bottom.linkTo(parent.bottom)
|
||||
// start.linkTo(parent.start)
|
||||
// })
|
||||
// Image(painter = painterResource(R.drawable.ic_rounded_corner_right), contentDescription = "right_corner",
|
||||
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_rounded_corner_right), contentDescription = "right_corner",
|
||||
// Modifier.width(12.dp).height(12.dp).constrainAs(image2) {
|
||||
// bottom.linkTo(parent.bottom)
|
||||
// end.linkTo(parent.end)
|
||||
|
|
|
@ -5,6 +5,7 @@ import ac.mdiq.podcini.databinding.LogsFragmentBinding
|
|||
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
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
|
@ -49,8 +50,10 @@ import androidx.compose.runtime.*
|
|||
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
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
|
@ -124,12 +127,27 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
} else {
|
||||
Logd(TAG, "shared log url: ${log.url}")
|
||||
// val episode = getEpisodeByGuidOrUrl(null, log.url!!, false)
|
||||
// if (episode != null) (activity as MainActivity).loadChildFragment(EpisodeInfoFragment.newInstance(episode))
|
||||
// else {
|
||||
var hasError = false
|
||||
when(log.type) {
|
||||
ShareLog.Type.YTMedia.name, "youtube media" -> {
|
||||
val episode = realm.query(Episode::class).query("title == $0", log.title).first().find()
|
||||
if (episode != null) (activity as MainActivity).loadChildFragment(EpisodeInfoFragment.newInstance(episode))
|
||||
else hasError = true
|
||||
}
|
||||
ShareLog.Type.Podcast.name, "podcast" -> {
|
||||
val feed = getFeedByTitleAndAuthor(log.title?:"", log.author?:"")
|
||||
if (feed != null ) (activity as MainActivity).loadChildFragment(FeedInfoFragment.newInstance(feed))
|
||||
else hasError = true
|
||||
}
|
||||
else -> {
|
||||
showSharedDialog.value = true
|
||||
sharedlogState.value = log
|
||||
}
|
||||
}
|
||||
if (hasError) {
|
||||
showSharedDialog.value = true
|
||||
sharedlogState.value = log
|
||||
// }
|
||||
}
|
||||
}
|
||||
}) {
|
||||
Column {
|
||||
|
@ -141,12 +159,13 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Spacer(Modifier.weight(1f))
|
||||
var showAction by remember { mutableStateOf(log.status < ShareLog.Status.SUCCESS.ordinal) }
|
||||
if (true || showAction) {
|
||||
Icon(painter = painterResource(R.drawable.ic_delete), tint = textColor, contentDescription = null,
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_delete), tint = textColor, contentDescription = null,
|
||||
modifier = Modifier.width(25.dp).height(25.dp).clickable {
|
||||
})
|
||||
}
|
||||
}
|
||||
Text(log.url?:"unknown", color = textColor)
|
||||
Text(log.title?:"unknown title", color = textColor)
|
||||
Text(log.url?:"unknown url", color = textColor)
|
||||
val statusText = when (log.status) {
|
||||
ShareLog.Status.ERROR.ordinal -> ShareLog.Status.ERROR.name
|
||||
ShareLog.Status.SUCCESS.ordinal -> ShareLog.Status.SUCCESS.name
|
||||
|
@ -185,7 +204,7 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
showDialog.value = true
|
||||
}) {
|
||||
val iconRes = remember { fromCode(log.rating).res }
|
||||
Icon(painter = painterResource(iconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
Icon(imageVector = ImageVector.vectorResource(iconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(40.dp).height(40.dp).padding(end = 15.dp))
|
||||
Column {
|
||||
Text(log.type + ": " + formatDateTimeFlex(Date(log.id)) + " -- " + formatDateTimeFlex(Date(log.cancelDate)), color = textColor)
|
||||
|
@ -245,7 +264,7 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
var showAction by remember { mutableStateOf(!status.isSuccessful && !newerWasSuccessful(position, status.feedfileType, status.feedfileId)) }
|
||||
if (showAction) {
|
||||
Icon(painter = painterResource(R.drawable.ic_refresh),
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_refresh),
|
||||
tint = textColor,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.width(28.dp).height(32.dp).clickable {
|
||||
|
|
|
@ -41,8 +41,10 @@ import androidx.compose.material3.Text
|
|||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.Insets
|
||||
|
@ -120,7 +122,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
|
|||
(activity as MainActivity).loadFragment(nav.tag, null)
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}) {
|
||||
Icon(painter = painterResource(nav.iconRes), tint = textColor, contentDescription = nav.tag, modifier = Modifier.padding(start = 10.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(nav.iconRes), tint = textColor, contentDescription = nav.tag, modifier = Modifier.padding(start = 10.dp))
|
||||
Text(stringResource(nav.nameRes), color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(start = 20.dp))
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (nav.count > 0) Text(nav.count.toString(), color = textColor, modifier = Modifier.padding(end = 10.dp))
|
||||
|
@ -146,7 +148,7 @@ class NavDrawerFragment : Fragment(), OnSharedPreferenceChangeListener {
|
|||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().clickable {
|
||||
startActivity(Intent(activity, PreferenceActivity::class.java))
|
||||
}) {
|
||||
Icon(painter = painterResource(R.drawable.ic_settings), tint = textColor, contentDescription = "settings", modifier = Modifier.padding(start = 10.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_settings), tint = textColor, contentDescription = "settings", modifier = Modifier.padding(start = 10.dp))
|
||||
Text(stringResource(R.string.settings_label), color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.padding(start = 20.dp))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,12 @@ import ac.mdiq.podcini.net.feed.discovery.PodcastSearcherRegistry
|
|||
import ac.mdiq.podcini.net.utils.HtmlToPlainText
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
|
||||
import ac.mdiq.podcini.storage.database.Feeds.getFeed
|
||||
import ac.mdiq.podcini.storage.database.Feeds.getFeedByTitleAndAuthor
|
||||
import ac.mdiq.podcini.storage.database.Feeds.getFeedList
|
||||
import ac.mdiq.podcini.storage.database.Feeds.isSubscribed
|
||||
import ac.mdiq.podcini.storage.database.Feeds.persistFeedPreferences
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.model.Rating.Companion.fromCode
|
||||
import ac.mdiq.podcini.storage.model.SubscriptionLog.Companion.feedLogsMap
|
||||
|
@ -44,9 +48,11 @@ import androidx.compose.material3.*
|
|||
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.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
@ -70,7 +76,6 @@ import kotlin.concurrent.Volatile
|
|||
* Downloads a feed from a feed URL and parses it. Subclasses can display the
|
||||
* feed object that was parsed. This activity MUST be started with a given URL
|
||||
* or an Exception will be thrown.
|
||||
*
|
||||
* If the feed cannot be downloaded or parsed, an error dialog will be displayed
|
||||
* and the activity will finish as soon as the error dialog is closed.
|
||||
*/
|
||||
|
@ -85,6 +90,8 @@ class OnlineFeedFragment : Fragment() {
|
|||
private var feedUrl: String = ""
|
||||
private lateinit var feedBuilder: FeedBuilder
|
||||
|
||||
private var isShared: Boolean = false
|
||||
|
||||
private var showFeedDisplay by mutableStateOf(false)
|
||||
private var showProgress by mutableStateOf(true)
|
||||
private var autoDownloadChecked by mutableStateOf(false)
|
||||
|
@ -123,6 +130,7 @@ class OnlineFeedFragment : Fragment() {
|
|||
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
|
||||
|
||||
feedUrl = requireArguments().getString(ARG_FEEDURL) ?: ""
|
||||
isShared = requireArguments().getBoolean("isShared")
|
||||
Logd(TAG, "feedUrl: $feedUrl")
|
||||
feedBuilder = FeedBuilder(requireContext()) { message, details -> showErrorDialog(message, details) }
|
||||
|
||||
|
@ -183,6 +191,13 @@ class OnlineFeedFragment : Fragment() {
|
|||
feedBuilder.startFeedBuilding(urlString, username, password) { feed_, map ->
|
||||
selectedDownloadUrl = feedBuilder.selectedDownloadUrl
|
||||
feed = feed_
|
||||
if (isShared) {
|
||||
val log = realm.query(ShareLog::class).query("url == $0", url).first().find()
|
||||
if (log != null) upsertBlk(log) {
|
||||
it.title = feed_.title
|
||||
it.author = feed_.author
|
||||
}
|
||||
}
|
||||
showFeedInformation(feed_, map)
|
||||
}
|
||||
} catch (e: FeedUrlNotFoundException) { tryToRetrieveFeedUrlBySearch(e)
|
||||
|
@ -216,6 +231,13 @@ class OnlineFeedFragment : Fragment() {
|
|||
feedBuilder.startFeedBuilding(url, username, password) { feed_, map ->
|
||||
selectedDownloadUrl = feedBuilder.selectedDownloadUrl
|
||||
feed = feed_
|
||||
if (isShared) {
|
||||
val log = realm.query(ShareLog::class).query("url == $0", url).first().find()
|
||||
if (log != null) upsertBlk(log) {
|
||||
it.title = feed_.title
|
||||
it.author = feed_.author
|
||||
}
|
||||
}
|
||||
showFeedInformation(feed_, map)
|
||||
}
|
||||
} else {
|
||||
|
@ -340,7 +362,7 @@ class OnlineFeedFragment : Fragment() {
|
|||
}) {
|
||||
if (showFeedDisplay) ConstraintLayout(modifier = Modifier.fillMaxWidth().height(120.dp).background(MaterialTheme.colorScheme.surface)) {
|
||||
val (backgroundImage, coverImage, taColumn, buttons, closeButton) = createRefs()
|
||||
if (false) Image(painter = painterResource(R.drawable.ic_settings_white), contentDescription = "background",
|
||||
if (false) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_settings_white), contentDescription = "background",
|
||||
Modifier.fillMaxWidth().height(120.dp).constrainAs(backgroundImage) {
|
||||
top.linkTo(parent.top)
|
||||
bottom.linkTo(parent.bottom)
|
||||
|
@ -364,12 +386,28 @@ class OnlineFeedFragment : Fragment() {
|
|||
}) {
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
if (enableSubscribe) Button(onClick = {
|
||||
if (feedInFeedlist()) (activity as MainActivity).loadFeedFragmentById(feedId, null)
|
||||
if (feedInFeedlist() || isSubscribed(feed!!)) {
|
||||
if (isShared) {
|
||||
val log = realm.query(ShareLog::class).query("url == $0", feedUrl).first().find()
|
||||
if (log != null) upsertBlk(log) {
|
||||
it.status = ShareLog.Status.EXISTING.ordinal
|
||||
}
|
||||
}
|
||||
val feed = getFeedByTitleAndAuthor(feed?.eigenTitle?:"", feed?.author?:"")
|
||||
if (feed != null ) (activity as MainActivity).loadChildFragment(FeedInfoFragment.newInstance(feed))
|
||||
// (activity as MainActivity).loadFeedFragmentById(feedId, null)
|
||||
}
|
||||
else {
|
||||
enableSubscribe = false
|
||||
enableEpisodes = false
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
feedBuilder.subscribe(feed!!)
|
||||
if (isShared) {
|
||||
val log = realm.query(ShareLog::class).query("url == $0", feedUrl).first().find()
|
||||
if (log != null) upsertBlk(log) {
|
||||
it.status = ShareLog.Status.SUCCESS.ordinal
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
enableSubscribe = true
|
||||
didPressSubscribe = true
|
||||
|
@ -386,7 +424,7 @@ class OnlineFeedFragment : Fragment() {
|
|||
}
|
||||
Spacer(modifier = Modifier.weight(0.2f))
|
||||
}
|
||||
if (false) Icon(painter = painterResource(R.drawable.ic_close_white), contentDescription = null, modifier = Modifier
|
||||
if (false) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_close_white), contentDescription = null, modifier = Modifier
|
||||
.constrainAs(closeButton) {
|
||||
top.linkTo(parent.top)
|
||||
end.linkTo(parent.end)
|
||||
|
@ -418,14 +456,14 @@ class OnlineFeedFragment : Fragment() {
|
|||
Text(HtmlToPlainText.getPlainText(feed?.description ?: ""), color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
val sLog = remember {feedLogsMap_[feed?.downloadUrl?:""] }
|
||||
if (sLog != null) {
|
||||
val commentTextState by remember { mutableStateOf(TextFieldValue(sLog.comment ?: "")) }
|
||||
val commentTextState by remember { mutableStateOf(TextFieldValue(sLog.comment)) }
|
||||
val context = LocalContext.current
|
||||
val cancelDate = remember { formatAbbrev(context, Date(sLog.cancelDate)) }
|
||||
val ratingRes = remember { fromCode(sLog.rating).res }
|
||||
if (commentTextState.text.isNotEmpty()) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp)) {
|
||||
Text(stringResource(R.string.my_opinion_label), color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.titleMedium)
|
||||
Icon(painter = painterResource(ratingRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = null, modifier = Modifier.padding(start = 5.dp))
|
||||
Icon(imageVector = ImageVector.vectorResource(ratingRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = null, modifier = Modifier.padding(start = 5.dp))
|
||||
}
|
||||
Text(commentTextState.text, color = textColor, style = MaterialTheme.typography.bodyMedium,
|
||||
modifier = Modifier.padding(start = 15.dp, bottom = 10.dp))
|
||||
|
@ -765,10 +803,11 @@ class OnlineFeedFragment : Fragment() {
|
|||
if (prefs == null) prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun newInstance(feedUrl: String): OnlineFeedFragment {
|
||||
fun newInstance(feedUrl: String, isShared: Boolean = false): OnlineFeedFragment {
|
||||
val fragment = OnlineFeedFragment()
|
||||
val b = Bundle()
|
||||
b.putString(ARG_FEEDURL, feedUrl)
|
||||
b.putBoolean("isShared", isShared)
|
||||
fragment.arguments = b
|
||||
return fragment
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ import kotlin.math.max
|
|||
swipeActionsBin = SwipeActions(this, "$TAG.Bin")
|
||||
swipeActionsBin.setFilter(EpisodeFilter(EpisodeFilter.States.queued.name))
|
||||
|
||||
binding.lazyColumn.setContent {
|
||||
binding.mainView.setContent {
|
||||
CustomTheme(requireContext()) {
|
||||
if (showBin) {
|
||||
Column {
|
||||
|
|
|
@ -18,6 +18,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsert
|
|||
import ac.mdiq.podcini.storage.model.*
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
|
||||
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.FeedAutoDeleteOptions
|
||||
import ac.mdiq.podcini.storage.utils.DurationConverter
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.compose.CustomTheme
|
||||
import ac.mdiq.podcini.ui.compose.RemoveFeedDialog
|
||||
|
@ -71,6 +72,7 @@ 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.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
|
@ -84,6 +86,8 @@ import androidx.fragment.app.Fragment
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.CachePolicy
|
||||
import coil.request.ImageRequest
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
|
@ -314,9 +318,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
CustomFeedNameDialog(activity as Activity, feed).show()
|
||||
}
|
||||
R.id.new_synth_yt -> {
|
||||
val feed = createSynthetic(0, "")
|
||||
val feed = createSynthetic(0, "", true)
|
||||
feed.type = Feed.FeedType.YOUTUBE.name
|
||||
feed.hasVideoMedia = true
|
||||
feed.preferences!!.videoModePolicy = VideoMode.WINDOW_VIEW
|
||||
CustomFeedNameDialog(activity as Activity, feed).show()
|
||||
}
|
||||
|
@ -498,7 +501,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
fun InforBar() {
|
||||
Row(Modifier.padding(start = 20.dp, end = 20.dp)) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Icon(painter = painterResource(R.drawable.ic_info), contentDescription = "info", tint = textColor)
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "info", tint = textColor)
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text(txtvInformation, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.clickable {
|
||||
if (feedsFilter.isNotEmpty()) {
|
||||
|
@ -839,6 +842,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
refreshing = false
|
||||
// }
|
||||
}) {
|
||||
val context = LocalContext.current
|
||||
if (if (useGrid == null) useGridLayout else useGrid!!) {
|
||||
val lazyGridState = rememberLazyGridState()
|
||||
LazyVerticalGrid(state = lazyGridState, columns = GridCells.Adaptive(80.dp),
|
||||
|
@ -881,8 +885,10 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
ConstraintLayout(Modifier.fillMaxSize()) {
|
||||
val (coverImage, episodeCount, rating, error) = createRefs()
|
||||
AsyncImage(model = feed.imageUrl, contentDescription = "coverImage",
|
||||
placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
val imgLoc = remember(feed) { feed.imageUrl }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "coverImage",
|
||||
modifier = Modifier.fillMaxWidth().aspectRatio(1f)
|
||||
.constrainAs(coverImage) {
|
||||
top.linkTo(parent.top)
|
||||
|
@ -895,13 +901,13 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
top.linkTo(coverImage.top)
|
||||
})
|
||||
if (feed.rating != Rating.UNRATED.code)
|
||||
Icon(painter = painterResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||
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)
|
||||
})
|
||||
// TODO: need to use state
|
||||
if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error",
|
||||
if (feed.lastUpdateFailed) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error",
|
||||
modifier = Modifier.background(Color.Gray).constrainAs(error) {
|
||||
end.linkTo(parent.end)
|
||||
bottom.linkTo(coverImage.bottom)
|
||||
|
@ -929,7 +935,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Row(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
|
||||
ConstraintLayout {
|
||||
val (coverImage, rating) = createRefs()
|
||||
AsyncImage(model = feed.imageUrl,
|
||||
val imgLoc = remember(feed) { feed.imageUrl }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "imgvCover",
|
||||
placeholder = painterResource(R.mipmap.ic_launcher),
|
||||
error = painterResource(R.mipmap.ic_launcher),
|
||||
|
@ -947,7 +955,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
})
|
||||
)
|
||||
if (feed.rating != Rating.UNRATED.code)
|
||||
Icon(painter = painterResource(Rating.fromCode(feed.rating).res), tint = MaterialTheme.colorScheme.tertiary,
|
||||
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)
|
||||
|
@ -980,8 +988,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
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)) {
|
||||
Text(NumberFormat.getInstance().format(feed.episodes.size.toLong()) + " episodes",
|
||||
color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
val measureString = remember { NumberFormat.getInstance().format(feed.episodes.size.toLong()) + " : " +
|
||||
DurationConverter.shortLocalizedDuration(requireActivity(), feed.totleDuration/1000) }
|
||||
Text(measureString, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
var feedSortInfo by remember { mutableStateOf(feed.sortInfo) }
|
||||
LaunchedEffect(feedSorted) { feedSortInfo = feed.sortInfo }
|
||||
|
@ -989,7 +998,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
}
|
||||
}
|
||||
// TODO: need to use state
|
||||
if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error")
|
||||
if (feed.lastUpdateFailed) Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -997,7 +1006,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
if (selectMode) {
|
||||
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp).background(Color.LightGray),
|
||||
horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
.clickable(onClick = {
|
||||
selected.clear()
|
||||
|
@ -1007,7 +1016,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
selectedSize = selected.size
|
||||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
|
||||
.clickable(onClick = {
|
||||
selected.clear()
|
||||
|
@ -1018,7 +1027,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
Logd(TAG, "selectedIds: ${selected.size}")
|
||||
}))
|
||||
var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) }
|
||||
Icon(painter = painterResource(selectAllRes), tint = Color.Black, contentDescription = null,
|
||||
Icon(imageVector = ImageVector.vectorResource(selectAllRes), tint = Color.Black, contentDescription = null,
|
||||
modifier = Modifier.width(35.dp).height(35.dp)
|
||||
.clickable(onClick = {
|
||||
if (selectedSize != feedListFiltered.size) {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/lazyColumn"
|
||||
android:id="@+id/mainView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
|
|
|
@ -1,3 +1,12 @@
|
|||
# 6.11.7
|
||||
|
||||
* added author and title info in SharedLog
|
||||
* when shared channel, playlist or podcast from Youtube, double checks if existing and records SharedLog accordingly
|
||||
* in Shared LogsFragment, tap on a successful or existing item (media or feed) opens the corresponding fragment
|
||||
* in Subscriptions view, added total duration for every feed
|
||||
* hasEmbeddedPicture in EpisodeMedia is set to not persist for now
|
||||
* tuned Compose routines ti reduce recomposition and improve efficiency
|
||||
|
||||
# 6.11.6
|
||||
|
||||
* fixed a serious performance issue when scrolling list of episode having no defined image url
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
Version 6.11.5
|
||||
Version 6.11.6
|
||||
|
||||
* fixed a serious performance issue when scrolling list of episode having no defined image url
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
Version 6.11.7
|
||||
|
||||
* added author and title info in SharedLog
|
||||
* when shared channel, playlist or podcast from Youtube, double checks if existing and records SharedLog accordingly
|
||||
* in Shared LogsFragment, tap on a successful or existing item (media or feed) opens the corresponding fragment
|
||||
* in Subscriptions view, added total duration for every feed
|
||||
* hasEmbeddedPicture in EpisodeMedia is set to not persist for now
|
||||
* tuned Compose routines ti reduce recomposition and improve efficiency
|
Loading…
Reference in New Issue