6.9.3 commit

This commit is contained in:
Xilin Jia 2024-10-09 19:52:40 +01:00
parent 3ba9134c6f
commit ac6d2bd105
21 changed files with 402 additions and 816 deletions

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests" testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020267 versionCode 3020268
versionName "6.9.2" versionName "6.9.3"
applicationId "ac.mdiq.podcini.R" applicationId "ac.mdiq.podcini.R"
def commit = "" def commit = ""

View File

@ -75,8 +75,8 @@ object EpisodeMenuHandler {
setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label_no_media) setItemTitle(menu, R.id.mark_unread_item, R.string.mark_unread_label_no_media)
} }
setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite) // setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite)
setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite) // setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite)
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
val fileDownloaded = withContext(Dispatchers.IO) { hasMedia && selectedItem.media?.fileExists() ?: false } val fileDownloaded = withContext(Dispatchers.IO) { hasMedia && selectedItem.media?.fileExists() ?: false }
@ -167,8 +167,8 @@ object EpisodeMenuHandler {
} }
R.id.add_to_queue_item -> addToQueue(true, selectedItem) R.id.add_to_queue_item -> addToQueue(true, selectedItem)
R.id.remove_from_queue_item -> removeFromQueue(selectedItem) R.id.remove_from_queue_item -> removeFromQueue(selectedItem)
R.id.add_to_favorites_item -> setFavorite(selectedItem, true) // R.id.add_to_favorites_item -> setFavorite(selectedItem, true)
R.id.remove_from_favorites_item -> setFavorite(selectedItem, false) // R.id.remove_from_favorites_item -> setFavorite(selectedItem, false)
R.id.reset_position -> { R.id.reset_position -> {
selectedItem.media?.setPosition(0) selectedItem.media?.setPosition(0)
if (curState.curMediaId == (selectedItem.media?.id ?: "")) { if (curState.curMediaId == (selectedItem.media?.id ?: "")) {

View File

@ -1,166 +0,0 @@
package ac.mdiq.podcini.ui.actions
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.SelectQueueDialogBinding
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.storage.database.Episodes
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.PlayQueue
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import android.app.Activity
import android.content.DialogInterface
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.RadioButton
import androidx.annotation.PluralsRes
import androidx.media3.common.util.UnstableApi
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.runBlocking
import java.lang.ref.WeakReference
@UnstableApi
class EpisodeMultiSelectHandler(private val activity: MainActivity, private val actionId: Int) {
private var totalNumItems = 0
private var snackbar: Snackbar? = null
fun handleAction(items: List<Episode>) {
when (actionId) {
R.id.toggle_favorite_batch -> toggleFavorite(items)
R.id.add_to_queue_batch -> queueChecked(items)
R.id.put_in_queue_batch -> PutToQueueDialog(activity, items).show()
R.id.remove_from_queue_batch -> removeFromQueueChecked(items)
R.id.toggle_played_batch -> {
setPlayState(Episode.PlayState.UNSPECIFIED.code, false, *items.toTypedArray())
// showMessage(R.plurals.marked_read_batch_label, items.size)
}
R.id.download_batch -> downloadChecked(items)
R.id.delete_batch -> deleteChecked(items)
else -> Log.e(TAG, "Unrecognized speed dial action item. Do nothing. id=$actionId")
}
}
private fun queueChecked(items: List<Episode>) {
// Check if an episode actually contains any media files before adding it to queue
// val toQueue = mutableListOf<Long>()
// for (episode in items) {
// if (episode.media != null) toQueue.add(episode.id)
// }
Queues.addToQueue(true, *items.toTypedArray())
showMessage(R.plurals.added_to_queue_batch_label, items.size)
}
private fun removeFromQueueChecked(items: List<Episode>) {
// val checkedIds = getSelectedIds(items)
removeFromQueue(*items.toTypedArray())
showMessage(R.plurals.removed_from_queue_batch_label, items.size)
}
private fun toggleFavorite(items: List<Episode>) {
for (item in items) {
Episodes.setFavorite(item, null)
}
showMessage(R.plurals.marked_favorite_batch_label, items.size)
}
private fun downloadChecked(items: List<Episode>) {
// download the check episodes in the same order as they are currently displayed
for (episode in items) {
if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode)
}
showMessage(R.plurals.downloading_batch_label, items.size)
}
private fun deleteChecked(items: List<Episode>) {
LocalDeleteModal.deleteEpisodesWarnLocal(activity, items)
showMessage(R.plurals.deleted_multi_episode_batch_label, items.size)
}
private fun showMessage(@PluralsRes msgId: Int, numItems: Int) {
totalNumItems += numItems
activity.runOnUiThread {
val text: String = activity.resources.getQuantityString(msgId, totalNumItems, totalNumItems)
if (snackbar != null) {
snackbar?.setText(text)
snackbar?.show() // Resets the timeout
} else snackbar = activity.showSnackbarAbovePlayer(text, Snackbar.LENGTH_LONG)
}
}
// private fun getSelectedIds(items: List<Episode>): List<Long> {
// val checkedIds = mutableListOf<Long>()
// for (i in items.indices) {
// checkedIds.add(items[i].id)
// }
// return checkedIds
// }
class PutToQueueDialog(activity: Activity, val items: List<Episode>) {
private val activityRef: WeakReference<Activity> = WeakReference(activity)
fun show() {
val activity = activityRef.get() ?: return
val binding = SelectQueueDialogBinding.inflate(LayoutInflater.from(activity))
binding.removeCheckbox.visibility = View.VISIBLE
val queues = realm.query(PlayQueue::class).find()
for (i in queues.indices) {
val radioButton = RadioButton(activity)
radioButton.text = queues[i].name
radioButton.textSize = 20f
radioButton.tag = i
binding.radioGroup.addView(radioButton)
}
var toQueue: PlayQueue = curQueue
binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
val radioButton = group.findViewById<RadioButton>(checkedId)
val selectedIndex = radioButton.tag as Int
toQueue = queues[selectedIndex]
}
val dialog = MaterialAlertDialogBuilder(activity)
.setView(binding.root)
.setTitle(R.string.put_in_queue_label)
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
// val queues = realm.query(PlayQueue::class).find()
if (binding.removeCheckbox.isChecked) {
val toRemove = mutableSetOf<Long>()
val toRemoveCur = mutableListOf<Episode>()
items.forEach { e ->
if (curQueue.contains(e)) toRemoveCur.add(e)
}
items.forEach { e ->
for (q in queues) {
if (q.contains(e)) {
toRemove.add(e.id)
break
}
}
}
if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) }
if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
}
items.forEach { e ->
runBlocking { addToQueueSync(false, e, toQueue) }
}
}
.setNegativeButton(R.string.cancel_label, null)
dialog.show()
}
}
companion object {
private val TAG: String = EpisodeMultiSelectHandler::class.simpleName ?: "Anonymous"
}
}

View File

@ -399,7 +399,7 @@ class MainActivity : CastEnabledActivity() {
navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0)) navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
mainView.layoutParams = params mainView.layoutParams = params
// val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1) // val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1)
val playerView = findViewById<ComposeView>(R.id.composeView1) val playerView = findViewById<ComposeView>(R.id.player1)
val playerParams = playerView?.layoutParams as? MarginLayoutParams val playerParams = playerView?.layoutParams as? MarginLayoutParams
playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0) playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
playerView?.layoutParams = playerParams playerView?.layoutParams = playerParams

View File

@ -9,6 +9,8 @@ import ac.mdiq.podcini.ui.compose.confirmAddYoutubeEpisode
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeServiceURL import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeServiceURL
import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeURL import ac.mdiq.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeURL
import android.app.Service.START_STICKY
import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@ -20,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.work.WorkerParameters
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.net.URL import java.net.URL
import java.net.URLDecoder import java.net.URLDecoder
@ -49,42 +52,18 @@ class ShareReceiverActivity : AppCompatActivity() {
Logd(TAG, "feedUrl: $sharedUrl") Logd(TAG, "feedUrl: $sharedUrl")
val log = ShareLog(sharedUrl!!) val log = ShareLog(sharedUrl!!)
upsertBlk(log) {} upsertBlk(log) {}
receiveShared(sharedUrl!!,this, true)
// val url = URL(sharedUrl) receiveShared(sharedUrl!!,this, true) {
// when { setContent {
//// plain text val showDialog = remember { mutableStateOf(true) }
// sharedUrl!!.matches(Regex("^[^\\s<>/]+\$")) -> { CustomTheme(this) {
// log = upsertBlk(log) {it.type = "text" } confirmAddYoutubeEpisode(listOf(sharedUrl!!), showDialog.value, onDismissRequest = {
// val intent = MainActivity.showOnlineSearch(this, sharedUrl!!) showDialog.value = false
// startActivity(intent)
// finish() // finish()
// } })
//// Youtube media }
//// sharedUrl!!.startsWith("https://youtube.com/watch?") || sharedUrl!!.startsWith("https://www.youtube.com/watch?") || sharedUrl!!.startsWith("https://music.youtube.com/watch?") -> { }
// (isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url) -> { }
// log = upsertBlk(log) {it.type = "youtube media" }
// Logd(TAG, "got youtube media")
// setContent {
// val showDialog = remember { mutableStateOf(true) }
// CustomTheme(this@ShareReceiverActivity) {
// confirmAddYoutubeEpisode(listOf(sharedUrl!!), showDialog.value, onDismissRequest = {
// showDialog.value = false
// finish()
// })
// }
// }
// }
//// podcast or Youtube channel, Youtube playlist, or other?
// else -> {
// log = upsertBlk(log) {it.type = "podcast" }
// Logd(TAG, "Activity was started with url $sharedUrl")
// val intent = MainActivity.showOnlineFeed(this, sharedUrl!!)
//// intent.putExtra(MainActivity.Extras.started_from_share.name, getIntent().getBooleanExtra(MainActivity.Extras.started_from_share.name, false))
// startActivity(intent)
// finish()
// }
// }
} }
private fun showNoPodcastFoundError() { private fun showNoPodcastFoundError() {
@ -105,13 +84,31 @@ class ShareReceiverActivity : AppCompatActivity() {
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out)
} }
// class UrlFetchWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
// override fun doWork(): Result {
// val url = inputData.getString("shared_url") ?: return Result.failure()
//
// // Fetch the content from URL using OkHttp or another HTTP client
// // Ensure you're on a background thread
//
// return Result.success()
// }
// }
// // Schedule the worker
// val workRequest = OneTimeWorkRequestBuilder<UrlFetchWorker>()
// .setInputData(workDataOf("shared_url" to sharedUrl))
// .build()
//
// WorkManager.getInstance(context).enqueue(workRequest)
companion object { companion object {
private val TAG: String = ShareReceiverActivity::class.simpleName ?: "Anonymous" private val TAG: String = ShareReceiverActivity::class.simpleName ?: "Anonymous"
const val ARG_FEEDURL: String = "arg.feedurl" const val ARG_FEEDURL: String = "arg.feedurl"
private const val RESULT_ERROR = 2 private const val RESULT_ERROR = 2
fun receiveShared(sharedUrl: String, activity: AppCompatActivity, finish: Boolean) { fun receiveShared(sharedUrl: String, activity: AppCompatActivity, finish: Boolean, mediaCB: ()->Unit) {
val url = URL(sharedUrl) val url = URL(sharedUrl)
val log = realm.query(ShareLog::class).query("url == $0", sharedUrl).first().find() val log = realm.query(ShareLog::class).query("url == $0", sharedUrl).first().find()
when { when {
@ -126,15 +123,7 @@ class ShareReceiverActivity : AppCompatActivity() {
(isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url) -> { (isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url) -> {
if (log != null) upsertBlk(log) {it.type = "youtube media" } if (log != null) upsertBlk(log) {it.type = "youtube media" }
Logd(TAG, "got youtube media") Logd(TAG, "got youtube media")
activity.setContent { mediaCB()
val showDialog = remember { mutableStateOf(true) }
CustomTheme(activity) {
confirmAddYoutubeEpisode(listOf(sharedUrl), showDialog.value, onDismissRequest = {
showDialog.value = false
if (finish) activity.finish()
})
}
}
} }
// podcast or Youtube channel, Youtube playlist, or other? // podcast or Youtube channel, Youtube playlist, or other?
else -> { else -> {

View File

@ -255,12 +255,12 @@ class VideoplayerActivity : CastEnabledActivity() {
val isItemHasDownloadLink = isEpisodeMedia && (media as EpisodeMedia?)?.downloadUrl != null val isItemHasDownloadLink = isEpisodeMedia && (media as EpisodeMedia?)?.downloadUrl != null
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink) menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
menu.findItem(R.id.add_to_favorites_item).setVisible(false) // menu.findItem(R.id.add_to_favorites_item).setVisible(false)
menu.findItem(R.id.remove_from_favorites_item).setVisible(false) // menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
if (isEpisodeMedia) { // if (isEpisodeMedia) {
menu.findItem(R.id.add_to_favorites_item).setVisible(!videoEpisodeFragment.isFavorite) // menu.findItem(R.id.add_to_favorites_item).setVisible(!videoEpisodeFragment.isFavorite)
menu.findItem(R.id.remove_from_favorites_item).setVisible(videoEpisodeFragment.isFavorite) // menu.findItem(R.id.remove_from_favorites_item).setVisible(videoEpisodeFragment.isFavorite)
} // }
menu.findItem(R.id.set_sleeptimer_item).setVisible(!isSleepTimerActive()) menu.findItem(R.id.set_sleeptimer_item).setVisible(!isSleepTimerActive())
menu.findItem(R.id.disable_sleeptimer_item).setVisible(isSleepTimerActive()) menu.findItem(R.id.disable_sleeptimer_item).setVisible(isSleepTimerActive())
@ -271,8 +271,8 @@ class VideoplayerActivity : CastEnabledActivity() {
menu.findItem(R.id.player_show_chapters).setVisible(true) menu.findItem(R.id.player_show_chapters).setVisible(true)
if (videoMode == VideoMode.WINDOW_VIEW) { if (videoMode == VideoMode.WINDOW_VIEW) {
menu.findItem(R.id.add_to_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER) // menu.findItem(R.id.add_to_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.remove_from_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER) // menu.findItem(R.id.remove_from_favorites_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.set_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.set_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.disable_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.disable_sleeptimer_item).setShowAsAction(SHOW_AS_ACTION_NEVER)
menu.findItem(R.id.player_switch_to_audio_only).setShowAsAction(SHOW_AS_ACTION_NEVER) menu.findItem(R.id.player_switch_to_audio_only).setShowAsAction(SHOW_AS_ACTION_NEVER)
@ -305,16 +305,16 @@ class VideoplayerActivity : CastEnabledActivity() {
val media = curMedia ?: return false val media = curMedia ?: return false
val feedItem = (media as? EpisodeMedia)?.episodeOrFetch() val feedItem = (media as? EpisodeMedia)?.episodeOrFetch()
when { when {
item.itemId == R.id.add_to_favorites_item && feedItem != null -> { // item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
setFavorite(feedItem, true) // setFavorite(feedItem, true)
videoEpisodeFragment.isFavorite = true // videoEpisodeFragment.isFavorite = true
invalidateOptionsMenu() // invalidateOptionsMenu()
} // }
item.itemId == R.id.remove_from_favorites_item && feedItem != null -> { // item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
setFavorite(feedItem, false) // setFavorite(feedItem, false)
videoEpisodeFragment.isFavorite = false // videoEpisodeFragment.isFavorite = false
invalidateOptionsMenu() // invalidateOptionsMenu()
} // }
item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item -> item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item ->
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog") SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
item.itemId == R.id.audio_controls -> { item.itemId == R.id.audio_controls -> {

View File

@ -13,38 +13,19 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun Spinner( fun Spinner(items: List<String>, selectedItem: String, onItemSelected: (String) -> Unit) {
items: List<String>,
selectedItem: String,
onItemSelected: (String) -> Unit
) {
var expanded by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) }
val context = LocalContext.current
ExposedDropdownMenuBox( ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
expanded = expanded, TextField(readOnly = true, value = selectedItem, onValueChange = {}, label = { Text("Select an item") },
onExpandedChange = { expanded = !expanded } modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable, true), // Material3 requirement
) { trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = ExposedDropdownMenuDefaults.textFieldColors())
TextField( ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
readOnly = true,
value = selectedItem,
onValueChange = {},
label = { Text("Select an item") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
items.forEach { item -> items.forEach { item ->
DropdownMenuItem(text = { Text(item) }, DropdownMenuItem(text = { Text(item) },
onClick = { onClick = {

View File

@ -12,21 +12,25 @@ import ac.mdiq.podcini.storage.database.Episodes.setPlayState
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
import ac.mdiq.podcini.storage.database.Queues import ac.mdiq.podcini.storage.database.Queues
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.MediaType import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.PlayQueue
import ac.mdiq.podcini.storage.model.ShareLog import ac.mdiq.podcini.storage.model.ShareLog
import ac.mdiq.podcini.storage.utils.DurationConverter import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.EpisodeActionButton import ac.mdiq.podcini.ui.actions.EpisodeActionButton
import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectHandler.PutToQueueDialog
import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.actions.SwipeAction
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
import ac.mdiq.podcini.ui.fragment.FeedInfoFragment import ac.mdiq.podcini.ui.fragment.FeedInfoFragment
import ac.mdiq.podcini.ui.utils.LocalDeleteModal import ac.mdiq.podcini.ui.utils.LocalDeleteModal
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev
import ac.mdiq.vista.extractor.Vista import ac.mdiq.vista.extractor.Vista
@ -173,6 +177,77 @@ class EpisodeVM(var episode: Episode) {
} }
} }
@Composable
fun ChooseRatingDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
Dialog(onDismissRequest = onDismissRequest) {
Surface(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
for (rating in Episode.Rating.entries) {
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))
}
}
}
}
}
}
@Composable
fun PutToQueueDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
val queues = realm.query(PlayQueue::class).find()
Dialog(onDismissRequest = onDismissRequest) {
Surface(shape = RoundedCornerShape(16.dp)) {
val scrollState = rememberScrollState()
Column(modifier = Modifier.verticalScroll(scrollState).padding(16.dp), verticalArrangement = Arrangement.spacedBy(1.dp)) {
var removeChecked by remember { mutableStateOf(false) }
var toQueue by remember { mutableStateOf(curQueue) }
for (q in queues) {
Row(verticalAlignment = Alignment.CenterVertically) {
RadioButton(selected = toQueue == q, onClick = { toQueue = q })
Text(q.name,)
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = removeChecked, onCheckedChange = { removeChecked = it })
Text(text = stringResource(R.string.remove_from_other_queues), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 10.dp))
}
Row {
Spacer(Modifier.weight(1f))
Button(onClick = {
if (removeChecked) {
val toRemove = mutableSetOf<Long>()
val toRemoveCur = mutableListOf<Episode>()
selected.forEach { e ->
if (curQueue.contains(e)) toRemoveCur.add(e)
}
selected.forEach { e ->
for (q in queues) {
if (q.contains(e)) {
toRemove.add(e.id)
break
}
}
}
if (toRemove.isNotEmpty()) runBlocking { removeFromAllQueuesQuiet(toRemove.toList()) }
if (toRemoveCur.isNotEmpty()) EventFlow.postEvent(FlowEvent.QueueEvent.removed(toRemoveCur))
}
selected.forEach { e ->
runBlocking { addToQueueSync(false, e, toQueue) }
}
onDismissRequest()
}) {
Text("Confirm")
}
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>, refreshCB: (()->Unit)? = null, fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>, refreshCB: (()->Unit)? = null,
@ -192,26 +267,11 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
showConfirmYoutubeDialog.value = false showConfirmYoutubeDialog.value = false
}) })
@Composable
fun ChooseRatingDialog(onDismissRequest: () -> Unit) {
Dialog(onDismissRequest = onDismissRequest) {
Surface(shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
for (rating in Episode.Rating.entries) {
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))
}
}
}
}
}
}
var showChooseRatingDialog by remember { mutableStateOf(false) } var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog { showChooseRatingDialog = false } if (showChooseRatingDialog) ChooseRatingDialog(selected) { showChooseRatingDialog = false }
var showPutToQueueDialog by remember { mutableStateOf(false) }
if (showPutToQueueDialog) PutToQueueDialog(selected) { showPutToQueueDialog = false }
@Composable @Composable
fun EpisodeSpeedDial(modifier: Modifier = Modifier) { fun EpisodeSpeedDial(modifier: Modifier = Modifier) {
@ -270,7 +330,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
isExpanded = false isExpanded = false
selectMode = false selectMode = false
Logd(TAG, "ic_playlist_play: ${selected.size}") Logd(TAG, "ic_playlist_play: ${selected.size}")
PutToQueueDialog(activity, selected).show() showPutToQueueDialog = true
// PutToQueueDialog(activity, selected).show()
}, verticalAlignment = Alignment.CenterVertically) { }, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "") Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "")
Text(stringResource(id = R.string.put_in_queue_label)) } }, Text(stringResource(id = R.string.put_in_queue_label)) } },
@ -325,7 +386,6 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
} }
var refreshing by remember { mutableStateOf(false)} var refreshing by remember { mutableStateOf(false)}
PullToRefreshBox(modifier = Modifier.fillMaxWidth(), isRefreshing = refreshing, indicator = {}, onRefresh = { PullToRefreshBox(modifier = Modifier.fillMaxWidth(), isRefreshing = refreshing, indicator = {}, onRefresh = {
refreshing = true refreshing = true
refreshCB?.invoke() refreshCB?.invoke()

View File

@ -36,6 +36,7 @@ import ac.mdiq.podcini.ui.actions.EpisodeMenuHandler
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
@ -136,7 +137,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var episodeDate by mutableStateOf("") private var episodeDate by mutableStateOf("")
private var chapterControlVisible by mutableStateOf(false) private var chapterControlVisible by mutableStateOf(false)
private var hasNextChapter by mutableStateOf(true) private var hasNextChapter by mutableStateOf(true)
// var imgLoc by mutableStateOf<String?>("") var rating by mutableStateOf(currentItem?.rating ?: 0)
private val currentChapter: Chapter? private val currentChapter: Chapter?
get() { get() {
if (currentMedia == null || currentMedia!!.getChapters().isEmpty() || displayedChapterIndex == -1) return null if (currentMedia == null || currentMedia!!.getChapters().isEmpty() || displayedChapterIndex == -1) return null
@ -160,7 +161,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
controller!!.init() controller!!.init()
onCollaped() onCollaped()
binding.composeView1.setContent { binding.player1.setContent {
CustomTheme(requireContext()) { CustomTheme(requireContext()) {
if (showPlayer1) PlayerUI() if (showPlayer1) PlayerUI()
else Spacer(modifier = Modifier.size(0.dp)) else Spacer(modifier = Modifier.size(0.dp))
@ -173,7 +174,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// else Spacer(modifier = Modifier.size(0.dp)) // else Spacer(modifier = Modifier.size(0.dp))
} }
} }
binding.composeView2.setContent { binding.player2.setContent {
CustomTheme(requireContext()) { CustomTheme(requireContext()) {
if (!showPlayer1) PlayerUI() if (!showPlayer1) PlayerUI()
else Spacer(modifier = Modifier.size(0.dp)) else Spacer(modifier = Modifier.size(0.dp))
@ -334,6 +335,11 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun DetailUI() { fun DetailUI() {
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) {
showChooseRatingDialog = false
}
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) { Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
@ -354,7 +360,16 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
}, onLongClick = { copyText(currentMedia?.getFeedTitle()?:"") })) }, onLongClick = { copyText(currentMedia?.getFeedTitle()?:"") }))
Text(episodeDate, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 2.dp), color = textColor, style = MaterialTheme.typography.bodyMedium) Row(modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 2.dp), ) {
Spacer(modifier = Modifier.weight(0.2f))
var ratingIconRes = Episode.Rating.fromCode(rating).res
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating", modifier = Modifier.width(15.dp).height(15.dp).clickable(onClick = {
showChooseRatingDialog = true
}))
Spacer(modifier = Modifier.weight(0.4f))
Text(episodeDate, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.bodyMedium)
Spacer(modifier = Modifier.weight(0.6f))
}
Text(titleText, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp) Text(titleText, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.titleLarge, modifier = Modifier.fillMaxWidth().padding(top = 2.dp, bottom = 5.dp)
.combinedClickable(onClick = {}, onLongClick = { copyText(currentItem?.title?:"") })) .combinedClickable(onClick = {}, onLongClick = { copyText(currentItem?.title?:"") }))
fun restoreFromPreference(): Boolean { fun restoreFromPreference(): Boolean {
@ -899,7 +914,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
onPlaybackServiceChanged(event) onPlaybackServiceChanged(event)
} }
is FlowEvent.PlayEvent -> onPlayEvent(event) is FlowEvent.PlayEvent -> onPlayEvent(event)
is FlowEvent.RatingEvent -> onFavoriteEvent(event) is FlowEvent.RatingEvent -> onRatingEvent(event)
is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event) is FlowEvent.PlayerErrorEvent -> MediaPlayerErrorDialog.show(activity as Activity, event)
// is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false) // is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) setupOptionsMenu() is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) setupOptionsMenu()
@ -911,8 +926,11 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
private fun onFavoriteEvent(event: FlowEvent.RatingEvent) { private fun onRatingEvent(event: FlowEvent.RatingEvent) {
if (curEpisode?.id == event.episode.id) EpisodeMenuHandler.onPrepareMenu(toolbar.menu, event.episode) if (curEpisode?.id == event.episode.id) {
rating = event.rating
EpisodeMenuHandler.onPrepareMenu(toolbar.menu, event.episode)
}
} }
private fun setupOptionsMenu() { private fun setupOptionsMenu() {
@ -982,7 +1000,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
fun fadePlayerToToolbar(slideOffset: Float) { fun fadePlayerToToolbar(slideOffset: Float) {
val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat() val playerFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.2f).toDouble())) / 0.2f).toFloat()
val player = binding.composeView1 val player = binding.player1
player.alpha = 1 - playerFadeProgress player.alpha = 1 - playerFadeProgress
player.visibility = if (playerFadeProgress > 0.99f) View.GONE else View.VISIBLE player.visibility = if (playerFadeProgress > 0.99f) View.GONE else View.VISIBLE
val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat() val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat()

View File

@ -11,6 +11,7 @@ import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo
import ac.mdiq.podcini.preferences.UsageStatistics import ac.mdiq.podcini.preferences.UsageStatistics
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsert
@ -22,6 +23,7 @@ import ac.mdiq.podcini.storage.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.ui.actions.* import ac.mdiq.podcini.ui.actions.*
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.utils.ShownotesCleaner import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.utils.ThemeUtils import ac.mdiq.podcini.ui.utils.ThemeUtils
@ -100,6 +102,15 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var itemLoaded = false private var itemLoaded = false
private var episode: Episode? = null // managed private var episode: Episode? = null // managed
private var txtvPodcast by mutableStateOf("")
private var txtvTitle by mutableStateOf("")
private var txtvPublished by mutableStateOf("")
private var txtvSize by mutableStateOf("")
private var txtvDuration by mutableStateOf("")
private var itemLink by mutableStateOf("")
var hasMedia by mutableStateOf(true)
var rating by mutableStateOf(episode?.rating ?: 0)
private var webviewData by mutableStateOf("") private var webviewData by mutableStateOf("")
private lateinit var shownotesCleaner: ShownotesCleaner private lateinit var shownotesCleaner: ShownotesCleaner
@ -188,6 +199,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
runOnIOScope { if (episode != null) episode = upsert(episode!!) { it.comment = commentTextState.text } } runOnIOScope { if (episode != null) episode = upsert(episode!!) { it.comment = commentTextState.text } }
}) })
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(listOf(episode!!)) {
showChooseRatingDialog = false
}
Column { Column {
Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) { Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) {
val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null
@ -199,7 +215,12 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(0.4f))
var ratingIconRes = Episode.Rating.fromCode(rating).res
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating", modifier = Modifier.width(15.dp).height(15.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(painter = painterResource(actionButton1?.getDrawable()?: R.drawable.ic_questionmark), tint = textColor, contentDescription = "butAction1",
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = { modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = {
when { when {
@ -235,7 +256,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}) })
// if (cond) CircularProgressIndicator(progress = 0.01f * dlPercent, strokeWidth = 4.dp, color = textColor) // if (cond) CircularProgressIndicator(progress = 0.01f * dlPercent, strokeWidth = 4.dp, color = textColor)
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(0.4f))
} }
if (!hasMedia) Text("noMediaLabel", color = textColor, style = MaterialTheme.typography.bodyMedium) if (!hasMedia) Text("noMediaLabel", color = textColor, style = MaterialTheme.typography.bodyMedium)
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
@ -367,12 +388,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
else EpisodeMenuHandler.onPrepareMenu(toolbar.menu, episode, R.id.open_podcast, R.id.mark_read_item, R.id.visit_website_item) else EpisodeMenuHandler.onPrepareMenu(toolbar.menu, episode, R.id.open_podcast, R.id.mark_read_item, R.id.visit_website_item)
} }
private var txtvPodcast by mutableStateOf("")
private var txtvTitle by mutableStateOf("")
private var txtvPublished by mutableStateOf("")
private var txtvSize by mutableStateOf("")
private var txtvDuration by mutableStateOf("")
private var itemLink by mutableStateOf("")
@UnstableApi @UnstableApi
private fun updateAppearance() { private fun updateAppearance() {
if (episode == null) { if (episode == null) {
@ -429,7 +444,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
updateButtons() updateButtons()
} }
var hasMedia by mutableStateOf(true)
@UnstableApi @UnstableApi
private fun updateButtons() { private fun updateButtons() {
// binding.circularProgressBar.visibility = View.GONE // binding.circularProgressBar.visibility = View.GONE
@ -501,7 +515,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "Received event: ${event.TAG}") Logd(TAG, "Received event: ${event.TAG}")
when (event) { when (event) {
is FlowEvent.QueueEvent -> onQueueEvent(event) is FlowEvent.QueueEvent -> onQueueEvent(event)
is FlowEvent.RatingEvent -> onFavoriteEvent(event) is FlowEvent.RatingEvent -> onRatingEvent(event)
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event) is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.PlayerSettingsEvent -> updateButtons() is FlowEvent.PlayerSettingsEvent -> updateButtons()
is FlowEvent.EpisodePlayedEvent -> load() is FlowEvent.EpisodePlayedEvent -> load()
@ -520,10 +534,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
private fun onFavoriteEvent(event: FlowEvent.RatingEvent) { private fun onRatingEvent(event: FlowEvent.RatingEvent) {
if (episode?.id == event.episode.id) { if (episode?.id == event.episode.id) {
episode = unmanaged(episode!!) episode = unmanaged(episode!!)
episode!!.rating = if (event.episode.isFavorite) Episode.Rating.FAVORITE.code else Episode.Rating.NEUTRAL.code episode!!.rating = event.rating
rating = episode!!.rating
// episode = event.episode // episode = event.episode
prepareMenu() prepareMenu()
} }
@ -587,6 +602,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
// binding.progbarLoading.visibility = View.GONE // binding.progbarLoading.visibility = View.GONE
rating = episode!!.rating
onFragmentLoaded() onFragmentLoaded()
itemLoaded = true itemLoaded = true
} }
@ -597,7 +613,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
fun setItem(item_: Episode) { fun setItem(item_: Episode) {
episode = item_ episode = realm.query(Episode::class).query("id == ${item_.id}").first().find()
} }
/** /**

View File

@ -104,16 +104,19 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
txtvAuthor = feed.author ?: "" txtvAuthor = feed.author ?: ""
txtvUrl = feed.downloadUrl txtvUrl = feed.downloadUrl
binding.header.setContent { // binding.header.setContent {
CustomTheme(requireContext()) { // CustomTheme(requireContext()) {
HeaderUI() // HeaderUI()
} // }
} // }
binding.detailUI.setContent { binding.detailUI.setContent {
CustomTheme(requireContext()) { CustomTheme(requireContext()) {
Column {
HeaderUI()
DetailUI() DetailUI()
} }
} }
}
// imgvBackground = binding.imgvBackground // imgvBackground = binding.imgvBackground
// https://github.com/bumptech/glide/issues/529 // https://github.com/bumptech/glide/issues/529

View File

@ -36,8 +36,10 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.*
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -93,26 +95,17 @@ class FeedSettingsFragment : Fragment() {
binding.composeView.setContent { binding.composeView.setContent {
CustomTheme(requireContext()) { CustomTheme(requireContext()) {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
Column( Column(modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if ((feed?.id ?: 0) > 10) { if ((feed?.id ?: 0) > 10) {
// refresh // refresh
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge, color = textColor)
text = stringResource(R.string.keep_updated),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated ?: true) } var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated ?: true) }
Switch( Switch(checked = checked, modifier = Modifier.height(24.dp),
checked = checked,
modifier = Modifier.height(24.dp),
onCheckedChange = { onCheckedChange = {
checked = it checked = it
feed = upsertBlk(feed!!) { f -> feed = upsertBlk(feed!!) { f ->
@ -121,41 +114,22 @@ class FeedSettingsFragment : Fragment() {
} }
) )
} }
Text( Text(text = stringResource(R.string.keep_updated_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.keep_updated_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
if ((feed?.id?:0) > 10 && feed?.hasVideoMedia == true) { if ((feed?.id?:0) > 10 && feed?.hasVideoMedia == true) {
// video mode // video mode
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) VideoModeDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.feed_video_mode_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.feed_video_mode_label), modifier = Modifier.clickable(onClick = { showDialog.value = true })
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
VideoModeDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
) )
} }
Text( Text(text = stringResource(videoModeSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(videoModeSummaryResId),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
if (feed?.type != Feed.FeedType.YOUTUBE.name) { if (feed?.type != Feed.FeedType.YOUTUBE.name) {
@ -164,18 +138,12 @@ class FeedSettingsFragment : Fragment() {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_stream), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_stream), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.pref_stream_over_download_title), style = MaterialTheme.typography.titleLarge, color = textColor)
text = stringResource(R.string.pref_stream_over_download_title),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { var checked by remember {
mutableStateOf(feed?.preferences?.prefStreamOverDownload ?: false) mutableStateOf(feed?.preferences?.prefStreamOverDownload ?: false)
} }
Switch( Switch(checked = checked, modifier = Modifier.height(24.dp),
checked = checked,
modifier = Modifier.height(24.dp),
onCheckedChange = { onCheckedChange = {
checked = it checked = it
feed = upsertBlk(feed!!) { f -> feed = upsertBlk(feed!!) { f ->
@ -184,61 +152,37 @@ class FeedSettingsFragment : Fragment() {
} }
) )
} }
Text( Text(text = stringResource(R.string.pref_stream_over_download_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.pref_stream_over_download_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
// associated queue // associated queue
Column { Column {
curPrefQueue = feed?.preferences?.queueTextExt ?: "Default" curPrefQueue = feed?.preferences?.queueTextExt ?: "Default"
var showDialog by remember { mutableStateOf(false) }
var selectedOption by remember { mutableStateOf(feed?.preferences?.queueText ?: "Default") }
if (showDialog) SetAssociatedQueue(showDialog, selectedOption = selectedOption, onDismissRequest = { showDialog = false })
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.pref_feed_associated_queue), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.pref_feed_associated_queue),
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
val selectedOption = feed?.preferences?.queueText ?: "Default" selectedOption = feed?.preferences?.queueText ?: "Default"
val composeView = ComposeView(requireContext()).apply { showDialog = true
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
SetAssociatedQueue(showDialog.value, selectedOption = selectedOption, onDismissRequest = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
}) })
) )
} }
Text( Text(text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// auto add new to queue // auto add new to queue
if (curPrefQueue != "None") { if (curPrefQueue != "None") {
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = androidx.media3.session.R.drawable.media3_icon_queue_add), Icon(ImageVector.vectorResource(id = androidx.media3.session.R.drawable.media3_icon_queue_add), "", tint = textColor)
"",
tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.audo_add_new_queue), style = MaterialTheme.typography.titleLarge, color = textColor)
text = stringResource(R.string.audo_add_new_queue),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue ?: true) } var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue ?: true) }
Switch( Switch(checked = checked, modifier = Modifier.height(24.dp),
checked = checked,
modifier = Modifier.height(24.dp),
onCheckedChange = { onCheckedChange = {
checked = it checked = it
feed = upsertBlk(feed!!) { f -> feed = upsertBlk(feed!!) { f ->
@ -247,41 +191,21 @@ class FeedSettingsFragment : Fragment() {
} }
) )
} }
Text( Text(text = stringResource(R.string.audo_add_new_queue_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.audo_add_new_queue_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
if (feed?.type != Feed.FeedType.YOUTUBE.name) { if (feed?.type != Feed.FeedType.YOUTUBE.name) {
// auto delete // auto delete
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_delete), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.auto_delete_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.auto_delete_label), modifier = Modifier.clickable(onClick = { showDialog.value = true }))
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
AutoDeleteDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
} }
} Text(text = stringResource(autoDeleteSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(autoDeleteSummaryResId),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
// tags // tags
@ -289,127 +213,64 @@ class FeedSettingsFragment : Fragment() {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_tag), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_tag), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.feed_tags_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.feed_tags_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
val dialog = TagSettingsDialog.newInstance(listOf(feed!!)) val dialog = TagSettingsDialog.newInstance(listOf(feed!!))
dialog.show(parentFragmentManager, TagSettingsDialog.TAG) dialog.show(parentFragmentManager, TagSettingsDialog.TAG)
}) })
) )
} }
Text( Text(text = stringResource(R.string.feed_tags_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.feed_tags_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// playback speed // playback speed
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_playback_speed), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_playback_speed), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.playback_speed), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.playback_speed),
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
PlaybackSpeedDialog().show() PlaybackSpeedDialog().show()
}) })
) )
} }
Text( Text(text = stringResource(R.string.pref_feed_playback_speed_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.pref_feed_playback_speed_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// auto skip // auto skip
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) AutoSkipDialog(showDialog.value, onDismiss = { showDialog.value = false })
Icon(ImageVector.vectorResource(id = R.drawable.ic_skip_24dp), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_skip_24dp), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.pref_feed_skip), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.pref_feed_skip), modifier = Modifier.clickable(onClick = { showDialog.value = true }))
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
AutoSkipDialog(showDialog.value, onDismiss = { showDialog.value = false })
} }
} Text(text = stringResource(R.string.pref_feed_skip_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.pref_feed_skip_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// volume adaption // volume adaption
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) VolumeAdaptionDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
Icon(ImageVector.vectorResource(id = R.drawable.ic_volume_adaption), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_volume_adaption), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.feed_volume_adapdation), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.feed_volume_adapdation), modifier = Modifier.clickable(onClick = { showDialog.value = true }))
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
VolumeAdaptionDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
} }
} Text(text = stringResource(R.string.feed_volume_adaptation_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.feed_volume_adaptation_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// authentication // authentication
if ((feed?.id?:0) > 0 && feed?.isLocalFeed != true) { if ((feed?.id?:0) > 0 && feed?.isLocalFeed != true) {
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) AuthenticationDialog(showDialog.value, onDismiss = { showDialog.value = false })
Icon(ImageVector.vectorResource(id = R.drawable.ic_key), "", tint = textColor) Icon(ImageVector.vectorResource(id = R.drawable.ic_key), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.authentication_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.authentication_label), modifier = Modifier.clickable(onClick = { showDialog.value = true }))
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
AuthenticationDialog(showDialog.value,
onDismiss = { showDialog.value = false })
} }
} Text(text = stringResource(R.string.authentication_descr), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.authentication_descr),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
if (isEnableAutodownload && feed?.type != Feed.FeedType.YOUTUBE.name) { if (isEnableAutodownload && feed?.type != Feed.FeedType.YOUTUBE.name) {
@ -417,15 +278,9 @@ class FeedSettingsFragment : Fragment() {
var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload ?: false) } var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload ?: false) }
Column { Column {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( Text(text = stringResource(R.string.auto_download_label), style = MaterialTheme.typography.titleLarge, color = textColor)
text = stringResource(R.string.auto_download_label),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Switch( Switch(checked = audoDownloadChecked, modifier = Modifier.height(24.dp),
checked = audoDownloadChecked,
modifier = Modifier.height(24.dp),
onCheckedChange = { onCheckedChange = {
audoDownloadChecked = it audoDownloadChecked = it
feed = upsertBlk(feed!!) { f -> f.preferences?.autoDownload = audoDownloadChecked } feed = upsertBlk(feed!!) { f -> f.preferences?.autoDownload = audoDownloadChecked }
@ -433,32 +288,18 @@ class FeedSettingsFragment : Fragment() {
) )
} }
if (!isEnableAutodownload) { if (!isEnableAutodownload) {
Text( Text(text = stringResource(R.string.auto_download_disabled_globally), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.auto_download_disabled_globally),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
if (audoDownloadChecked) { if (audoDownloadChecked) {
// auto download policy // auto download policy
Column (modifier = Modifier.padding(start = 20.dp)){ Column (modifier = Modifier.padding(start = 20.dp)){
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( val showDialog = remember { mutableStateOf(false) }
text = stringResource(R.string.feed_auto_download_policy), if (showDialog.value) AutoDownloadPolicyDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
style = MaterialTheme.typography.titleLarge, Text(text = stringResource(R.string.feed_auto_download_policy), style = MaterialTheme.typography.titleLarge, color = textColor,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply { showDialog.value = true
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
AutoDownloadPolicyDialog(showDialog.value,
onDismissRequest = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
}) })
) )
} }
@ -466,45 +307,23 @@ class FeedSettingsFragment : Fragment() {
// episode cache // episode cache
Column (modifier = Modifier.padding(start = 20.dp)) { Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( val showDialog = remember { mutableStateOf(false) }
text = stringResource(R.string.pref_episode_cache_title), if (showDialog.value) SetEpisodesCacheDialog(showDialog.value, onDismiss = { showDialog.value = false })
style = MaterialTheme.typography.titleLarge, Text(text = stringResource(R.string.pref_episode_cache_title), style = MaterialTheme.typography.titleLarge, color = textColor,
color = textColor, modifier = Modifier.clickable(onClick = { showDialog.value = true })
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
SetEpisodesCacheDialog(showDialog.value,
onDismiss = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
) )
} }
Text( Text(text = stringResource(R.string.pref_episode_cache_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.pref_episode_cache_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// counting played // counting played
Column (modifier = Modifier.padding(start = 20.dp)) { Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( Text(text = stringResource(R.string.pref_auto_download_counting_played_title), style = MaterialTheme.typography.titleLarge, color = textColor)
text = stringResource(R.string.pref_auto_download_counting_played_title),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { var checked by remember {
mutableStateOf(feed?.preferences?.countingPlayed ?: true) mutableStateOf(feed?.preferences?.countingPlayed ?: true)
} }
Switch( Switch(checked = checked, modifier = Modifier.height(24.dp),
checked = checked,
modifier = Modifier.height(24.dp),
onCheckedChange = { onCheckedChange = {
checked = it checked = it
feed = upsertBlk(feed!!) { f -> feed = upsertBlk(feed!!) { f ->
@ -513,19 +332,12 @@ class FeedSettingsFragment : Fragment() {
} }
) )
} }
Text( Text(text = stringResource(R.string.pref_auto_download_counting_played_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.pref_auto_download_counting_played_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// inclusive filter // inclusive filter
Column (modifier = Modifier.padding(start = 20.dp)) { Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( Text(text = stringResource(R.string.episode_inclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.episode_inclusive_filters_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
object : AutoDownloadFilterPrefDialog(requireContext(), object : AutoDownloadFilterPrefDialog(requireContext(),
feed?.preferences!!.autoDownloadFilter!!, feed?.preferences!!.autoDownloadFilter!!,
@ -540,19 +352,12 @@ class FeedSettingsFragment : Fragment() {
}) })
) )
} }
Text( Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.episode_filters_description),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
// exclusive filter // exclusive filter
Column (modifier = Modifier.padding(start = 20.dp)) { Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Text( Text(text = stringResource(R.string.episode_exclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor,
text = stringResource(R.string.episode_exclusive_filters_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
modifier = Modifier.clickable(onClick = { modifier = Modifier.clickable(onClick = {
object : AutoDownloadFilterPrefDialog(requireContext(), object : AutoDownloadFilterPrefDialog(requireContext(),
feed?.preferences!!.autoDownloadFilter!!, feed?.preferences!!.autoDownloadFilter!!,
@ -567,20 +372,14 @@ class FeedSettingsFragment : Fragment() {
}) })
) )
} }
Text( Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor)
text = stringResource(R.string.episode_filters_description),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
} }
} }
} }
} }
} }
} }
if (feed != null) { if (feed != null) toolbar.subtitle = feed!!.title
toolbar.subtitle = feed!!.title
}
return binding.root return binding.root
} }
@ -617,23 +416,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) { if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(videoMode) } val (selectedOption, onOptionSelected) = remember { mutableStateOf(videoMode) }
Dialog(onDismissRequest = { onDismissRequest() }) { Dialog(onDismissRequest = { onDismissRequest() }) {
Card( Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
modifier = Modifier Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column { Column {
videoModeTags.forEach { text -> videoModeTags.forEach { text ->
Row(Modifier Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = (text == selectedOption), Checkbox(checked = (text == selectedOption),
onCheckedChange = { onCheckedChange = {
Logd(TAG, "row clicked: $text $selectedOption") Logd(TAG, "row clicked: $text $selectedOption")
@ -652,12 +439,7 @@ class FeedSettingsFragment : Fragment() {
} }
} }
) )
Text( Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
} }
} }
} }
@ -688,23 +470,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) { if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(autoDeletePolicy) } val (selectedOption, onOptionSelected) = remember { mutableStateOf(autoDeletePolicy) }
Dialog(onDismissRequest = { onDismissRequest() }) { Dialog(onDismissRequest = { onDismissRequest() }) {
Card( Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
modifier = Modifier Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column { Column {
FeedAutoDeleteOptions.forEach { text -> FeedAutoDeleteOptions.forEach { text ->
Row(Modifier Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = (text == selectedOption), Checkbox(checked = (text == selectedOption),
onCheckedChange = { onCheckedChange = {
Logd(TAG, "row clicked: $text $selectedOption") Logd(TAG, "row clicked: $text $selectedOption")
@ -722,12 +492,7 @@ class FeedSettingsFragment : Fragment() {
} }
} }
) )
Text( Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
} }
} }
} }
@ -742,23 +507,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) { if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.volumeAdaptionSetting ?: VolumeAdaptionSetting.OFF) } val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.volumeAdaptionSetting ?: VolumeAdaptionSetting.OFF) }
Dialog(onDismissRequest = { onDismissRequest() }) { Dialog(onDismissRequest = { onDismissRequest() }) {
Card( Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
modifier = Modifier Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column { Column {
VolumeAdaptionSetting.entries.forEach { item -> VolumeAdaptionSetting.entries.forEach { item ->
Row(Modifier Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = (item == selectedOption), Checkbox(checked = (item == selectedOption),
onCheckedChange = { _ -> onCheckedChange = { _ ->
Logd(TAG, "row clicked: $item $selectedOption") Logd(TAG, "row clicked: $item $selectedOption")
@ -769,12 +522,7 @@ class FeedSettingsFragment : Fragment() {
} }
} }
) )
Text( Text(text = stringResource(item.resId), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
text = stringResource(item.resId),
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
} }
} }
} }
@ -789,16 +537,8 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) { if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.autoDLPolicy ?: AutoDownloadPolicy.ONLY_NEW) } val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.autoDLPolicy ?: AutoDownloadPolicy.ONLY_NEW) }
Dialog(onDismissRequest = { onDismissRequest() }) { Dialog(onDismissRequest = { onDismissRequest() }) {
Card( Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
modifier = Modifier Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column { Column {
AutoDownloadPolicy.entries.forEach { item -> AutoDownloadPolicy.entries.forEach { item ->
Row(Modifier Row(Modifier
@ -817,12 +557,7 @@ class FeedSettingsFragment : Fragment() {
} }
} }
) )
Text( Text(text = stringResource(item.resId), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
text = stringResource(item.resId),
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
} }
} }
} }
@ -836,17 +571,10 @@ class FeedSettingsFragment : Fragment() {
fun SetEpisodesCacheDialog(showDialog: Boolean, onDismiss: () -> Unit) { fun SetEpisodesCacheDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) { if (showDialog) {
Dialog(onDismissRequest = onDismiss) { Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
.wrapContentSize(align = Alignment.Center) Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
var newCache by remember { mutableStateOf((feed?.preferences?.autoDLMaxEpisodes ?: 1).toString()) } var newCache by remember { mutableStateOf((feed?.preferences?.autoDLMaxEpisodes ?: 1).toString()) }
TextField(value = newCache, TextField(value = newCache, onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) newCache = it },
onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) newCache = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
// visualTransformation = PositiveIntegerTransform(), // visualTransformation = PositiveIntegerTransform(),
label = { Text("Max episodes allowed") } label = { Text("Max episodes allowed") }
@ -870,18 +598,10 @@ class FeedSettingsFragment : Fragment() {
var selected by remember {mutableStateOf(selectedOption)} var selected by remember {mutableStateOf(selectedOption)}
if (showDialog) { if (showDialog) {
Dialog(onDismissRequest = { onDismissRequest() }) { Dialog(onDismissRequest = { onDismissRequest() }) {
Card(modifier = Modifier Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
.wrapContentSize(align = Alignment.Center) Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
queueSettingOptions.forEach { option -> queueSettingOptions.forEach { option ->
Row(modifier = Modifier.fillMaxWidth(), Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(checked = option == selected, Checkbox(checked = option == selected,
onCheckedChange = { isChecked -> onCheckedChange = { isChecked ->
selected = option selected = option
@ -911,6 +631,7 @@ class FeedSettingsFragment : Fragment() {
} }
if (selected == "Custom") { if (selected == "Custom") {
if (queues == null) queues = realm.query(PlayQueue::class).find() if (queues == null) queues = realm.query(PlayQueue::class).find()
Logd(TAG, "queues: ${queues?.size}")
Spinner(items = queues!!.map { it.name }, selectedItem = feed?.preferences?.queue?.name ?: "Default") { name -> Spinner(items = queues!!.map { it.name }, selectedItem = feed?.preferences?.queue?.name ?: "Default") { name ->
Logd(TAG, "Queue selected: $name") Logd(TAG, "Queue selected: $name")
val q = queues?.firstOrNull { it.name == name } val q = queues?.firstOrNull { it.name == name }
@ -929,26 +650,14 @@ class FeedSettingsFragment : Fragment() {
fun AuthenticationDialog(showDialog: Boolean, onDismiss: () -> Unit) { fun AuthenticationDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) { if (showDialog) {
Dialog(onDismissRequest = onDismiss) { Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
.wrapContentSize(align = Alignment.Center) Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val oldName = feed?.preferences?.username?:"" val oldName = feed?.preferences?.username?:""
var newName by remember { mutableStateOf(oldName) } var newName by remember { mutableStateOf(oldName) }
TextField(value = newName, TextField(value = newName, onValueChange = { newName = it }, label = { Text("Username") })
onValueChange = { newName = it },
label = { Text("Username") }
)
val oldPW = feed?.preferences?.password?:"" val oldPW = feed?.preferences?.password?:""
var newPW by remember { mutableStateOf(oldPW) } var newPW by remember { mutableStateOf(oldPW) }
TextField(value = newPW, TextField(value = newPW, onValueChange = { newPW = it }, label = { Text("Password") })
onValueChange = { newPW = it },
label = { Text("Password") }
)
Button(onClick = { Button(onClick = {
if (newName.isNotEmpty() && oldName != newName) { if (newName.isNotEmpty() && oldName != newName) {
feed = upsertBlk(feed!!) { feed = upsertBlk(feed!!) {
@ -971,14 +680,8 @@ class FeedSettingsFragment : Fragment() {
fun AutoSkipDialog(showDialog: Boolean, onDismiss: () -> Unit) { fun AutoSkipDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) { if (showDialog) {
Dialog(onDismissRequest = onDismiss) { Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
.wrapContentSize(align = Alignment.Center) Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
var intro by remember { mutableStateOf((feed?.preferences?.introSkip ?: 0).toString()) } var intro by remember { mutableStateOf((feed?.preferences?.introSkip ?: 0).toString()) }
TextField(value = intro, TextField(value = intro,
onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) intro = it }, onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) intro = it },

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.model.ShareLog import ac.mdiq.podcini.storage.model.ShareLog
import ac.mdiq.podcini.ui.activity.ShareReceiverActivity.Companion.receiveShared import ac.mdiq.podcini.ui.activity.ShareReceiverActivity.Companion.receiveShared
import ac.mdiq.podcini.ui.compose.CustomTheme import ac.mdiq.podcini.ui.compose.CustomTheme
import ac.mdiq.podcini.ui.compose.confirmAddYoutubeEpisode
import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.FlowEvent
import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.Logd
@ -43,6 +44,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -82,12 +84,13 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val showDialog = remember { mutableStateOf(false) } val showDialog = remember { mutableStateOf(false) }
val dialogParam = remember { mutableStateOf(ShareLog()) } val dialogParam = remember { mutableStateOf(ShareLog()) }
if (showDialog.value) { if (showDialog.value) {
DetailDialog( DetailDialog(status = dialogParam.value, showDialog = showDialog.value, onDismissRequest = { showDialog.value = false })
status = dialogParam.value,
showDialog = showDialog.value,
onDismissRequest = { showDialog.value = false },
)
} }
var showYTMediaConfirmDialog by remember { mutableStateOf(false) }
var sharedUrl by remember { mutableStateOf("") }
if (showYTMediaConfirmDialog)
confirmAddYoutubeEpisode(listOf(sharedUrl), showYTMediaConfirmDialog, onDismissRequest = { showYTMediaConfirmDialog = false })
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 6.dp, top = 5.dp, bottom = 5.dp), LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 6.dp, top = 5.dp, bottom = 5.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) { verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(logs) { position, log -> itemsIndexed(logs) { position, log ->
@ -96,7 +99,12 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (log.status == 1) { if (log.status == 1) {
showDialog.value = true showDialog.value = true
dialogParam.value = log dialogParam.value = log
} else receiveShared(log.url!!, activity as AppCompatActivity, false) } else {
receiveShared(log.url!!, activity as AppCompatActivity, false) {
sharedUrl = log.url!!
showYTMediaConfirmDialog = true
}
}
}) { }) {
Column { Column {
Row { Row {
@ -104,6 +112,13 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val iconColor = remember { if (log.status == 1) Color.Green else Color.Yellow } val iconColor = remember { if (log.status == 1) Color.Green else Color.Yellow }
Icon(icon, "Info", tint = iconColor, modifier = Modifier.padding(end = 2.dp)) Icon(icon, "Info", tint = iconColor, modifier = Modifier.padding(end = 2.dp))
Text(formatDateTimeFlex(Date(log.id)), color = textColor) Text(formatDateTimeFlex(Date(log.id)), color = textColor)
Spacer(Modifier.weight(1f))
var showAction by remember { mutableStateOf(log.status != 1) }
if (true || showAction) {
Icon(painter = painterResource(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.url?:"unknown", color = textColor)
val statusText = remember {"" } val statusText = remember {"" }
@ -113,15 +128,6 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Text(stringResource(R.string.download_error_tap_for_details), color = textColor) Text(stringResource(R.string.download_error_tap_for_details), color = textColor)
} }
} }
var showAction by remember { mutableStateOf(log.status != 1) }
if (showAction) {
Icon(painter = painterResource(R.drawable.ic_refresh),
tint = textColor,
contentDescription = null,
modifier = Modifier.width(28.dp).height(32.dp).clickable {
})
}
} }
} }
} }
@ -154,7 +160,7 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
try { try {
val result = withContext(Dispatchers.IO) { val result = withContext(Dispatchers.IO) {
Logd(TAG, "getDownloadLog() called") Logd(TAG, "getDownloadLog() called")
val dlog = realm.query(ShareLog::class).find().toMutableList() val dlog = realm.query(ShareLog::class).sort("id", Sort.DESCENDING).find().toMutableList()
realm.copyFromRealm(dlog) realm.copyFromRealm(dlog)
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -529,9 +529,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Column { Column {
FeedAutoDeleteOptions.forEach { text -> FeedAutoDeleteOptions.forEach { text ->
Row( Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp)
Modifier.fillMaxWidth().padding(horizontal = 16.dp).selectable( .selectable(selected = (text == selectedOption),
selected = (text == selectedOption),
onClick = { onClick = {
if (text != selectedOption) { if (text != selectedOption) {
val autoDeleteAction: AutoDeleteAction = AutoDeleteAction.fromTag(text) val autoDeleteAction: AutoDeleteAction = AutoDeleteAction.fromTag(text)
@ -545,11 +544,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
RadioButton(selected = (text == selectedOption), onClick = { }) RadioButton(selected = (text == selectedOption), onClick = { })
Text( Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
modifier = Modifier.padding(start = 16.dp)
)
} }
} }
} }
@ -641,27 +636,15 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
CustomTheme(activity) { CustomTheme(activity) {
if (showDialog.value) { if (showDialog.value) {
Dialog(onDismissRequest = { showDialog.value = false }) { Dialog(onDismissRequest = { showDialog.value = false }) {
Card( Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
modifier = Modifier Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.Center) {
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
Row(Modifier.fillMaxWidth()) { Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "") Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "")
Spacer(modifier = Modifier.width(20.dp)) Spacer(modifier = Modifier.width(20.dp))
Text( Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge)
text = stringResource(R.string.keep_updated),
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(false) } var checked by remember { mutableStateOf(false) }
Switch( Switch(checked = checked,
checked = checked,
onCheckedChange = { onCheckedChange = {
checked = it checked = it
saveFeedPreferences { pref: FeedPreferences -> saveFeedPreferences { pref: FeedPreferences ->
@ -670,10 +653,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
) )
} }
Text( Text(text = stringResource(R.string.keep_updated_summary), style = MaterialTheme.typography.bodyMedium)
text = stringResource(R.string.keep_updated_summary),
style = MaterialTheme.typography.bodyMedium
)
} }
} }
} }
@ -799,9 +779,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) { Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) {
if (isExpanded) options.forEachIndexed { _, button -> 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() }
containerColor = Color.LightGray,
onClick = {}) { button() }
} }
FloatingActionButton(containerColor = MaterialTheme.colorScheme.secondaryContainer, FloatingActionButton(containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.secondary, contentColor = MaterialTheme.colorScheme.secondary,
@ -826,10 +804,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}) { }) {
if (if (useGrid == null) useGridLayout else useGrid!!) { if (if (useGrid == null) useGridLayout else useGrid!!) {
val lazyGridState = rememberLazyGridState() val lazyGridState = rememberLazyGridState()
LazyVerticalGrid(state = lazyGridState, LazyVerticalGrid(state = lazyGridState, columns = GridCells.Fixed(3),
columns = GridCells.Fixed(3), verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp) contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp)
) { ) {
items(feedListFiltered.size, key = {index -> feedListFiltered[index].id}) { index -> items(feedListFiltered.size, key = {index -> feedListFiltered[index].id}) { index ->
@ -867,8 +843,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val textColor = MaterialTheme.colorScheme.onSurface val textColor = MaterialTheme.colorScheme.onSurface
ConstraintLayout { ConstraintLayout {
val (coverImage, episodeCount, error) = createRefs() val (coverImage, episodeCount, error) = createRefs()
AsyncImage(model = feed.imageUrl, contentDescription = "coverImage", AsyncImage(model = feed.imageUrl, contentDescription = "coverImage", placeholder = painterResource(R.mipmap.ic_launcher),
placeholder = painterResource(R.mipmap.ic_launcher),
modifier = Modifier modifier = Modifier
.constrainAs(coverImage) { .constrainAs(coverImage) {
top.linkTo(parent.top) top.linkTo(parent.top)
@ -881,24 +856,19 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
top.linkTo(coverImage.top) top.linkTo(coverImage.top)
}) })
// TODO: need to use state // TODO: need to use state
if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red, if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error",
contentDescription = "error",
modifier = Modifier.constrainAs(error) { modifier = Modifier.constrainAs(error) {
end.linkTo(parent.end) end.linkTo(parent.end)
bottom.linkTo(coverImage.bottom) bottom.linkTo(coverImage.bottom)
}) })
} }
Text(feed.title ?: "No title", Text(feed.title ?: "No title", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
color = textColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis)
} }
} }
} }
} else { } else {
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
LazyColumn(state = lazyListState, LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp),
modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) { verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(feedListFiltered, key = {index, feed -> feed.id}) { index, feed -> itemsIndexed(feedListFiltered, key = {index, feed -> feed.id}) { index, feed ->
var isSelected by remember { mutableStateOf(false) } var isSelected by remember { mutableStateOf(false) }
@ -912,8 +882,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "toggleSelected: selected: ${selected.size}") Logd(TAG, "toggleSelected: selected: ${selected.size}")
} }
Row(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) { Row(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover", AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher),
placeholder = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(80.dp).height(80.dp) modifier = Modifier.width(80.dp).height(80.dp)
.clickable(onClick = { .clickable(onClick = {
Logd(TAG, "icon clicked!") Logd(TAG, "icon clicked!")
@ -964,13 +933,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
} }
if (selectMode) { if (selectMode) {
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp) Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp).background(Color.LightGray),
.background(Color.LightGray), horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
horizontalArrangement = Arrangement.Center, Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
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) modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = { .clickable(onClick = {
selected.clear() selected.clear()
@ -980,9 +945,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
selectedSize = selected.size selectedSize = selected.size
Logd(TAG, "selectedIds: ${selected.size}") Logd(TAG, "selectedIds: ${selected.size}")
})) }))
Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
tint = Color.Black,
contentDescription = null,
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp) modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = { .clickable(onClick = {
selected.clear() selected.clear()
@ -993,9 +956,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "selectedIds: ${selected.size}") Logd(TAG, "selectedIds: ${selected.size}")
})) }))
var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) } var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) }
Icon(painter = painterResource(selectAllRes), Icon(painter = painterResource(selectAllRes), tint = Color.Black, contentDescription = null,
tint = Color.Black,
contentDescription = null,
modifier = Modifier.width(35.dp).height(35.dp) modifier = Modifier.width(35.dp).height(35.dp)
.clickable(onClick = { .clickable(onClick = {
if (selectedSize != feedListFiltered.size) { if (selectedSize != feedListFiltered.size) {

View File

@ -9,7 +9,7 @@
android:orientation="vertical"> android:orientation="vertical">
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView1" android:id="@+id/player1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/external_player_height" android:layout_height="@dimen/external_player_height"
android:elevation="8dp" android:elevation="8dp"
@ -38,7 +38,7 @@
android:layout_marginBottom="12dp"/> android:layout_marginBottom="12dp"/>
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView2" android:id="@+id/player2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="@dimen/external_player_height"/> android:layout_height="@dimen/external_player_height"/>

View File

@ -25,10 +25,10 @@
app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" /> app:navigationIcon="?homeAsUpIndicator" />
<androidx.compose.ui.platform.ComposeView <!-- <androidx.compose.ui.platform.ComposeView-->
android:id="@+id/header" <!-- android:id="@+id/header"-->
android:layout_width="match_parent" <!-- android:layout_width="match_parent"-->
android:layout_height="wrap_content"/> <!-- android:layout_height="wrap_content"/>-->
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView

View File

@ -34,17 +34,17 @@
android:title="@string/remove_from_queue_label"> android:title="@string/remove_from_queue_label">
</item> </item>
<item <!-- <item-->
android:id="@+id/add_to_favorites_item" <!-- android:id="@+id/add_to_favorites_item"-->
android:icon="@drawable/ic_star_border" <!-- android:icon="@drawable/ic_star_border"-->
custom:showAsAction="always" <!-- custom:showAsAction="always"-->
android:title="@string/add_to_favorite_label" /> <!-- android:title="@string/add_to_favorite_label" />-->
<item <!-- <item-->
android:id="@+id/remove_from_favorites_item" <!-- android:id="@+id/remove_from_favorites_item"-->
android:icon="@drawable/ic_star" <!-- android:icon="@drawable/ic_star"-->
custom:showAsAction="always" <!-- custom:showAsAction="always"-->
android:title="@string/remove_from_favorite_label" /> <!-- android:title="@string/remove_from_favorite_label" />-->
<item <item
android:id="@+id/reset_position" android:id="@+id/reset_position"

View File

@ -30,15 +30,15 @@
android:menuCategory="container" android:menuCategory="container"
android:title="@string/delete_label" /> android:title="@string/delete_label" />
<item <!-- <item-->
android:id="@+id/add_to_favorites_item" <!-- android:id="@+id/add_to_favorites_item"-->
android:menuCategory="container" <!-- android:menuCategory="container"-->
android:title="@string/add_to_favorite_label" /> <!-- android:title="@string/add_to_favorite_label" />-->
<item <!-- <item-->
android:id="@+id/remove_from_favorites_item" <!-- android:id="@+id/remove_from_favorites_item"-->
android:menuCategory="container" <!-- android:menuCategory="container"-->
android:title="@string/remove_from_favorite_label" /> <!-- android:title="@string/remove_from_favorite_label" />-->
<item <item
android:id="@+id/reset_position" android:id="@+id/reset_position"

View File

@ -18,18 +18,18 @@
custom:showAsAction="always"> custom:showAsAction="always">
</item> </item>
<item <!-- <item-->
android:id="@+id/add_to_favorites_item" <!-- android:id="@+id/add_to_favorites_item"-->
android:icon="@drawable/ic_star_border" <!-- android:icon="@drawable/ic_star_border"-->
android:title="@string/add_to_favorite_label" <!-- android:title="@string/add_to_favorite_label"-->
custom:showAsAction="always"> <!-- custom:showAsAction="always">-->
</item> <!-- </item>-->
<item <!-- <item-->
android:id="@+id/remove_from_favorites_item" <!-- android:id="@+id/remove_from_favorites_item"-->
android:icon="@drawable/ic_star" <!-- android:icon="@drawable/ic_star"-->
android:title="@string/remove_from_favorite_label" <!-- android:title="@string/remove_from_favorite_label"-->
custom:showAsAction="always"> <!-- custom:showAsAction="always">-->
</item> <!-- </item>-->
<item <item
android:id="@+id/disable_sleeptimer_item" android:id="@+id/disable_sleeptimer_item"

View File

@ -1,3 +1,11 @@
# 6.9.3
* fixed app quit issue when repairing a shared item
* fixed custom queue spinner not showing up (issue introduced in 6.8.3 when migrating to Material3)
* updated dialog popup mechanism in FeedSettings
* updated UI to reflect the new rating system
* a couple dialog converted to Compose and removed EpisodeMultiSelectHandler
# 6.9.2 # 6.9.2
* fixed getting 0 episodes with Youtube playlist etc * fixed getting 0 episodes with Youtube playlist etc

View File

@ -0,0 +1,7 @@
Version 6.9.3
* fixed app quit issue when repairing a shared item
* fixed custom queue spinner not showing up (issue introduced in 6.8.3 when migrating to Material3)
* updated dialog popup mechanism in FeedSettings
* updated UI to reflect the new rating system
* a couple dialog converted to Compose and removed EpisodeMultiSelectHandler