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"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020267
versionName "6.9.2"
versionCode 3020268
versionName "6.9.3"
applicationId "ac.mdiq.podcini.R"
def commit = ""

View File

@ -75,8 +75,8 @@ object EpisodeMenuHandler {
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.remove_from_favorites_item, isFavorite)
// setItemVisibility(menu, R.id.add_to_favorites_item, !isFavorite)
// setItemVisibility(menu, R.id.remove_from_favorites_item, isFavorite)
CoroutineScope(Dispatchers.Main).launch {
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.remove_from_queue_item -> removeFromQueue(selectedItem)
R.id.add_to_favorites_item -> setFavorite(selectedItem, true)
R.id.remove_from_favorites_item -> setFavorite(selectedItem, false)
// R.id.add_to_favorites_item -> setFavorite(selectedItem, true)
// R.id.remove_from_favorites_item -> setFavorite(selectedItem, false)
R.id.reset_position -> {
selectedItem.media?.setPosition(0)
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))
mainView.layoutParams = params
// 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
playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
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.vista.extractor.services.youtube.YoutubeParsingHelper.isYoutubeServiceURL
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.Intent
import android.net.Uri
@ -20,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.media3.common.util.UnstableApi
import androidx.work.WorkerParameters
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import java.net.URL
import java.net.URLDecoder
@ -49,42 +52,18 @@ class ShareReceiverActivity : AppCompatActivity() {
Logd(TAG, "feedUrl: $sharedUrl")
val log = ShareLog(sharedUrl!!)
upsertBlk(log) {}
receiveShared(sharedUrl!!,this, true)
// val url = URL(sharedUrl)
// when {
//// plain text
// sharedUrl!!.matches(Regex("^[^\\s<>/]+\$")) -> {
// log = upsertBlk(log) {it.type = "text" }
// val intent = MainActivity.showOnlineSearch(this, sharedUrl!!)
// startActivity(intent)
receiveShared(sharedUrl!!,this, true) {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(this) {
confirmAddYoutubeEpisode(listOf(sharedUrl!!), showDialog.value, onDismissRequest = {
showDialog.value = false
// 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() {
@ -105,13 +84,31 @@ class ShareReceiverActivity : AppCompatActivity() {
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 {
private val TAG: String = ShareReceiverActivity::class.simpleName ?: "Anonymous"
const val ARG_FEEDURL: String = "arg.feedurl"
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 log = realm.query(ShareLog::class).query("url == $0", sharedUrl).first().find()
when {
@ -126,15 +123,7 @@ class ShareReceiverActivity : AppCompatActivity() {
(isYoutubeURL(url) && url.path.startsWith("/watch")) || isYoutubeServiceURL(url) -> {
if (log != null) upsertBlk(log) {it.type = "youtube media" }
Logd(TAG, "got youtube media")
activity.setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(activity) {
confirmAddYoutubeEpisode(listOf(sharedUrl), showDialog.value, onDismissRequest = {
showDialog.value = false
if (finish) activity.finish()
})
}
}
mediaCB()
}
// podcast or Youtube channel, Youtube playlist, or other?
else -> {

View File

@ -255,12 +255,12 @@ class VideoplayerActivity : CastEnabledActivity() {
val isItemHasDownloadLink = isEpisodeMedia && (media as EpisodeMedia?)?.downloadUrl != null
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
menu.findItem(R.id.add_to_favorites_item).setVisible(false)
menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
if (isEpisodeMedia) {
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.add_to_favorites_item).setVisible(false)
// menu.findItem(R.id.remove_from_favorites_item).setVisible(false)
// if (isEpisodeMedia) {
// 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.set_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)
if (videoMode == VideoMode.WINDOW_VIEW) {
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.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.set_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)
@ -305,16 +305,16 @@ class VideoplayerActivity : CastEnabledActivity() {
val media = curMedia ?: return false
val feedItem = (media as? EpisodeMedia)?.episodeOrFetch()
when {
item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
setFavorite(feedItem, true)
videoEpisodeFragment.isFavorite = true
invalidateOptionsMenu()
}
item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
setFavorite(feedItem, false)
videoEpisodeFragment.isFavorite = false
invalidateOptionsMenu()
}
// item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
// setFavorite(feedItem, true)
// videoEpisodeFragment.isFavorite = true
// invalidateOptionsMenu()
// }
// item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
// setFavorite(feedItem, false)
// videoEpisodeFragment.isFavorite = false
// invalidateOptionsMenu()
// }
item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item ->
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun Spinner(
items: List<String>,
selectedItem: String,
onItemSelected: (String) -> Unit
) {
fun Spinner(items: List<String>, selectedItem: String, onItemSelected: (String) -> Unit) {
var expanded by remember { mutableStateOf(false) }
val context = LocalContext.current
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = !expanded }
) {
TextField(
readOnly = true,
value = selectedItem,
onValueChange = {},
label = { Text("Select an item") },
trailingIcon = {
ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded)
},
colors = ExposedDropdownMenuDefaults.textFieldColors()
)
ExposedDropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false }
) {
ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { expanded = it }) {
TextField(readOnly = true, value = selectedItem, onValueChange = {}, label = { Text("Select an item") },
modifier = Modifier.menuAnchor(MenuAnchorType.PrimaryNotEditable, true), // Material3 requirement
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, colors = ExposedDropdownMenuDefaults.textFieldColors())
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
items.forEach { item ->
DropdownMenuItem(text = { Text(item) },
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.addToYoutubeSyndicate
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.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode
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.utils.DurationConverter
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
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.activity.MainActivity
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
import ac.mdiq.podcini.ui.fragment.FeedInfoFragment
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.MiscFormatter.formatAbbrev
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)
@Composable
fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>, refreshCB: (()->Unit)? = null,
@ -192,26 +267,11 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
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) }
if (showChooseRatingDialog) ChooseRatingDialog { showChooseRatingDialog = false }
if (showChooseRatingDialog) ChooseRatingDialog(selected) { showChooseRatingDialog = false }
var showPutToQueueDialog by remember { mutableStateOf(false) }
if (showPutToQueueDialog) PutToQueueDialog(selected) { showPutToQueueDialog = false }
@Composable
fun EpisodeSpeedDial(modifier: Modifier = Modifier) {
@ -270,7 +330,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
isExpanded = false
selectMode = false
Logd(TAG, "ic_playlist_play: ${selected.size}")
PutToQueueDialog(activity, selected).show()
showPutToQueueDialog = true
// PutToQueueDialog(activity, selected).show()
}, verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "")
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)}
PullToRefreshBox(modifier = Modifier.fillMaxWidth(), isRefreshing = refreshing, indicator = {}, onRefresh = {
refreshing = true
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.VideoplayerActivity.Companion.videoMode
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.dialog.MediaPlayerErrorDialog
import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
@ -136,7 +137,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var episodeDate by mutableStateOf("")
private var chapterControlVisible by mutableStateOf(false)
private var hasNextChapter by mutableStateOf(true)
// var imgLoc by mutableStateOf<String?>("")
var rating by mutableStateOf(currentItem?.rating ?: 0)
private val currentChapter: Chapter?
get() {
if (currentMedia == null || currentMedia!!.getChapters().isEmpty() || displayedChapterIndex == -1) return null
@ -160,7 +161,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
controller!!.init()
onCollaped()
binding.composeView1.setContent {
binding.player1.setContent {
CustomTheme(requireContext()) {
if (showPlayer1) PlayerUI()
else Spacer(modifier = Modifier.size(0.dp))
@ -173,7 +174,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// else Spacer(modifier = Modifier.size(0.dp))
}
}
binding.composeView2.setContent {
binding.player2.setContent {
CustomTheme(requireContext()) {
if (!showPlayer1) PlayerUI()
else Spacer(modifier = Modifier.size(0.dp))
@ -334,6 +335,11 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DetailUI() {
var showChooseRatingDialog by remember { mutableStateOf(false) }
if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) {
showChooseRatingDialog = false
}
val scrollState = rememberScrollState()
Column(modifier = Modifier.fillMaxWidth().verticalScroll(scrollState)) {
val textColor = MaterialTheme.colorScheme.onSurface
@ -354,7 +360,16 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
}, 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)
.combinedClickable(onClick = {}, onLongClick = { copyText(currentItem?.title?:"") }))
fun restoreFromPreference(): Boolean {
@ -899,7 +914,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
onPlaybackServiceChanged(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.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) loadMediaInfo(false)
is FlowEvent.SleepTimerUpdatedEvent -> if (event.isCancelled || event.wasJustEnabled()) setupOptionsMenu()
@ -911,8 +926,11 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
private fun onFavoriteEvent(event: FlowEvent.RatingEvent) {
if (curEpisode?.id == event.episode.id) EpisodeMenuHandler.onPrepareMenu(toolbar.menu, event.episode)
private fun onRatingEvent(event: FlowEvent.RatingEvent) {
if (curEpisode?.id == event.episode.id) {
rating = event.rating
EpisodeMenuHandler.onPrepareMenu(toolbar.menu, event.episode)
}
}
private fun setupOptionsMenu() {
@ -982,7 +1000,7 @@ class AudioPlayerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
fun fadePlayerToToolbar(slideOffset: Float) {
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.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()

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.preferences.UsageStatistics
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.unmanaged
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.ui.actions.*
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.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.utils.ThemeUtils
@ -100,6 +102,15 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var itemLoaded = false
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 lateinit var shownotesCleaner: ShownotesCleaner
@ -188,6 +199,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
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 {
Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp), verticalAlignment = Alignment.CenterVertically) {
val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null
@ -199,7 +215,12 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
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",
modifier = Modifier.width(24.dp).height(24.dp).clickable(onClick = {
when {
@ -235,7 +256,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
})
// 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)
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)
}
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
private fun updateAppearance() {
if (episode == null) {
@ -429,7 +444,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
updateButtons()
}
var hasMedia by mutableStateOf(true)
@UnstableApi
private fun updateButtons() {
// binding.circularProgressBar.visibility = View.GONE
@ -501,7 +515,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "Received event: ${event.TAG}")
when (event) {
is FlowEvent.QueueEvent -> onQueueEvent(event)
is FlowEvent.RatingEvent -> onFavoriteEvent(event)
is FlowEvent.RatingEvent -> onRatingEvent(event)
is FlowEvent.EpisodeEvent -> onEpisodeEvent(event)
is FlowEvent.PlayerSettingsEvent -> updateButtons()
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) {
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
prepareMenu()
}
@ -587,6 +602,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
withContext(Dispatchers.Main) {
// binding.progbarLoading.visibility = View.GONE
rating = episode!!.rating
onFragmentLoaded()
itemLoaded = true
}
@ -597,7 +613,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
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 ?: ""
txtvUrl = feed.downloadUrl
binding.header.setContent {
CustomTheme(requireContext()) {
HeaderUI()
}
}
// binding.header.setContent {
// CustomTheme(requireContext()) {
// HeaderUI()
// }
// }
binding.detailUI.setContent {
CustomTheme(requireContext()) {
Column {
HeaderUI()
DetailUI()
}
}
}
// imgvBackground = binding.imgvBackground
// 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.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -93,26 +95,17 @@ class FeedSettingsFragment : Fragment() {
binding.composeView.setContent {
CustomTheme(requireContext()) {
val textColor = MaterialTheme.colorScheme.onSurface
Column(
modifier = Modifier.padding(start = 20.dp, end = 16.dp, top = 10.dp, bottom = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Column(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) {
// refresh
Column {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.keep_updated),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge, color = textColor)
Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(feed?.preferences?.keepUpdated ?: true) }
Switch(
checked = checked,
modifier = Modifier.height(24.dp),
Switch(checked = checked, modifier = Modifier.height(24.dp),
onCheckedChange = {
checked = it
feed = upsertBlk(feed!!) { f ->
@ -121,41 +114,22 @@ class FeedSettingsFragment : Fragment() {
}
)
}
Text(
text = stringResource(R.string.keep_updated_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.keep_updated_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
if ((feed?.id?:0) > 10 && feed?.hasVideoMedia == true) {
// video mode
Column {
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)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.feed_video_mode_label),
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 = stringResource(R.string.feed_video_mode_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true })
)
}
Text(
text = stringResource(videoModeSummaryResId),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(videoModeSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
if (feed?.type != Feed.FeedType.YOUTUBE.name) {
@ -164,18 +138,12 @@ class FeedSettingsFragment : Fragment() {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_stream), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.pref_stream_over_download_title),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Text(text = stringResource(R.string.pref_stream_over_download_title), style = MaterialTheme.typography.titleLarge, color = textColor)
Spacer(modifier = Modifier.weight(1f))
var checked by remember {
mutableStateOf(feed?.preferences?.prefStreamOverDownload ?: false)
}
Switch(
checked = checked,
modifier = Modifier.height(24.dp),
Switch(checked = checked, modifier = Modifier.height(24.dp),
onCheckedChange = {
checked = it
feed = upsertBlk(feed!!) { f ->
@ -184,61 +152,37 @@ class FeedSettingsFragment : Fragment() {
}
)
}
Text(
text = stringResource(R.string.pref_stream_over_download_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.pref_stream_over_download_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
// associated queue
Column {
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()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_playlist_play), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.pref_feed_associated_queue),
style = MaterialTheme.typography.titleLarge,
color = textColor,
Text(text = stringResource(R.string.pref_feed_associated_queue), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
val selectedOption = feed?.preferences?.queueText ?: "Default"
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
SetAssociatedQueue(showDialog.value, selectedOption = selectedOption, onDismissRequest = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
selectedOption = feed?.preferences?.queueText ?: "Default"
showDialog = true
})
)
}
Text(
text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = curPrefQueue + " : " + stringResource(R.string.pref_feed_associated_queue_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// auto add new to queue
if (curPrefQueue != "None") {
Column {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = androidx.media3.session.R.drawable.media3_icon_queue_add),
"",
tint = textColor)
Icon(ImageVector.vectorResource(id = androidx.media3.session.R.drawable.media3_icon_queue_add), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.audo_add_new_queue),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Text(text = stringResource(R.string.audo_add_new_queue), style = MaterialTheme.typography.titleLarge, color = textColor)
Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(feed?.preferences?.autoAddNewToQueue ?: true) }
Switch(
checked = checked,
modifier = Modifier.height(24.dp),
Switch(checked = checked, modifier = Modifier.height(24.dp),
onCheckedChange = {
checked = it
feed = upsertBlk(feed!!) { f ->
@ -247,41 +191,21 @@ class FeedSettingsFragment : Fragment() {
}
)
}
Text(
text = stringResource(R.string.audo_add_new_queue_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.audo_add_new_queue_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
if (feed?.type != Feed.FeedType.YOUTUBE.name) {
// auto delete
Column {
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)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.auto_delete_label),
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(R.string.auto_delete_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true }))
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(autoDeleteSummaryResId),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(autoDeleteSummaryResId), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
// tags
@ -289,127 +213,64 @@ class FeedSettingsFragment : Fragment() {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_tag), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.feed_tags_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
Text(text = stringResource(R.string.feed_tags_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
val dialog = TagSettingsDialog.newInstance(listOf(feed!!))
dialog.show(parentFragmentManager, TagSettingsDialog.TAG)
})
)
}
Text(
text = stringResource(R.string.feed_tags_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.feed_tags_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// playback speed
Column {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_playback_speed), "", tint = textColor)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.playback_speed),
style = MaterialTheme.typography.titleLarge,
color = textColor,
Text(text = stringResource(R.string.playback_speed), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
PlaybackSpeedDialog().show()
})
)
}
Text(
text = stringResource(R.string.pref_feed_playback_speed_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.pref_feed_playback_speed_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// auto skip
Column {
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)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.pref_feed_skip),
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), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true }))
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.pref_feed_skip_sum),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.pref_feed_skip_sum), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// volume adaption
Column {
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)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.feed_volume_adapdation),
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_adapdation), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true }))
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.feed_volume_adaptation_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.feed_volume_adaptation_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// authentication
if ((feed?.id?:0) > 0 && feed?.isLocalFeed != true) {
Column {
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)
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.authentication_label),
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_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true }))
}
}
}
(view as? ViewGroup)?.addView(composeView)
})
)
}
Text(
text = stringResource(R.string.authentication_descr),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.authentication_descr), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
if (isEnableAutodownload && feed?.type != Feed.FeedType.YOUTUBE.name) {
@ -417,15 +278,9 @@ class FeedSettingsFragment : Fragment() {
var audoDownloadChecked by remember { mutableStateOf(feed?.preferences?.autoDownload ?: false) }
Column {
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.auto_download_label),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Text(text = stringResource(R.string.auto_download_label), style = MaterialTheme.typography.titleLarge, color = textColor)
Spacer(modifier = Modifier.weight(1f))
Switch(
checked = audoDownloadChecked,
modifier = Modifier.height(24.dp),
Switch(checked = audoDownloadChecked, modifier = Modifier.height(24.dp),
onCheckedChange = {
audoDownloadChecked = it
feed = upsertBlk(feed!!) { f -> f.preferences?.autoDownload = audoDownloadChecked }
@ -433,32 +288,18 @@ class FeedSettingsFragment : Fragment() {
)
}
if (!isEnableAutodownload) {
Text(
text = stringResource(R.string.auto_download_disabled_globally),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.auto_download_disabled_globally), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
if (audoDownloadChecked) {
// auto download policy
Column (modifier = Modifier.padding(start = 20.dp)){
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.feed_auto_download_policy),
style = MaterialTheme.typography.titleLarge,
color = textColor,
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) AutoDownloadPolicyDialog(showDialog.value, onDismissRequest = { showDialog.value = false })
Text(text = stringResource(R.string.feed_auto_download_policy), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
val composeView = ComposeView(requireContext()).apply {
setContent {
val showDialog = remember { mutableStateOf(true) }
CustomTheme(requireContext()) {
AutoDownloadPolicyDialog(showDialog.value,
onDismissRequest = { showDialog.value = false })
}
}
}
(view as? ViewGroup)?.addView(composeView)
showDialog.value = true
})
)
}
@ -466,45 +307,23 @@ class FeedSettingsFragment : Fragment() {
// episode cache
Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.pref_episode_cache_title),
style = MaterialTheme.typography.titleLarge,
color = textColor,
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)
})
val showDialog = remember { mutableStateOf(false) }
if (showDialog.value) SetEpisodesCacheDialog(showDialog.value, onDismiss = { showDialog.value = false })
Text(text = stringResource(R.string.pref_episode_cache_title), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = { showDialog.value = true })
)
}
Text(
text = stringResource(R.string.pref_episode_cache_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.pref_episode_cache_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// counting played
Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.pref_auto_download_counting_played_title),
style = MaterialTheme.typography.titleLarge,
color = textColor
)
Text(text = stringResource(R.string.pref_auto_download_counting_played_title), style = MaterialTheme.typography.titleLarge, color = textColor)
Spacer(modifier = Modifier.weight(1f))
var checked by remember {
mutableStateOf(feed?.preferences?.countingPlayed ?: true)
}
Switch(
checked = checked,
modifier = Modifier.height(24.dp),
Switch(checked = checked, modifier = Modifier.height(24.dp),
onCheckedChange = {
checked = it
feed = upsertBlk(feed!!) { f ->
@ -513,19 +332,12 @@ class FeedSettingsFragment : Fragment() {
}
)
}
Text(
text = stringResource(R.string.pref_auto_download_counting_played_summary),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.pref_auto_download_counting_played_summary), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// inclusive filter
Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.episode_inclusive_filters_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
Text(text = stringResource(R.string.episode_inclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
object : AutoDownloadFilterPrefDialog(requireContext(),
feed?.preferences!!.autoDownloadFilter!!,
@ -540,19 +352,12 @@ class FeedSettingsFragment : Fragment() {
})
)
}
Text(
text = stringResource(R.string.episode_filters_description),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
// exclusive filter
Column (modifier = Modifier.padding(start = 20.dp)) {
Row(Modifier.fillMaxWidth()) {
Text(
text = stringResource(R.string.episode_exclusive_filters_label),
style = MaterialTheme.typography.titleLarge,
color = textColor,
Text(text = stringResource(R.string.episode_exclusive_filters_label), style = MaterialTheme.typography.titleLarge, color = textColor,
modifier = Modifier.clickable(onClick = {
object : AutoDownloadFilterPrefDialog(requireContext(),
feed?.preferences!!.autoDownloadFilter!!,
@ -567,20 +372,14 @@ class FeedSettingsFragment : Fragment() {
})
)
}
Text(
text = stringResource(R.string.episode_filters_description),
style = MaterialTheme.typography.bodyMedium,
color = textColor
)
Text(text = stringResource(R.string.episode_filters_description), style = MaterialTheme.typography.bodyMedium, color = textColor)
}
}
}
}
}
}
if (feed != null) {
toolbar.subtitle = feed!!.title
}
if (feed != null) toolbar.subtitle = feed!!.title
return binding.root
}
@ -617,23 +416,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(videoMode) }
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Column {
videoModeTags.forEach { text ->
Row(Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = (text == selectedOption),
onCheckedChange = {
Logd(TAG, "row clicked: $text $selectedOption")
@ -652,12 +439,7 @@ class FeedSettingsFragment : Fragment() {
}
}
)
Text(
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
}
}
}
@ -688,23 +470,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(autoDeletePolicy) }
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Column {
FeedAutoDeleteOptions.forEach { text ->
Row(Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = (text == selectedOption),
onCheckedChange = {
Logd(TAG, "row clicked: $text $selectedOption")
@ -722,12 +492,7 @@ class FeedSettingsFragment : Fragment() {
}
}
)
Text(
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
}
}
}
@ -742,23 +507,11 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.volumeAdaptionSetting ?: VolumeAdaptionSetting.OFF) }
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Column {
VolumeAdaptionSetting.entries.forEach { item ->
Row(Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp), verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = (item == selectedOption),
onCheckedChange = { _ ->
Logd(TAG, "row clicked: $item $selectedOption")
@ -769,12 +522,7 @@ class FeedSettingsFragment : Fragment() {
}
}
)
Text(
text = stringResource(item.resId),
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
Text(text = stringResource(item.resId), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
}
}
}
@ -789,16 +537,8 @@ class FeedSettingsFragment : Fragment() {
if (showDialog) {
val (selectedOption, onOptionSelected) = remember { mutableStateOf(feed?.preferences?.autoDLPolicy ?: AutoDownloadPolicy.ONLY_NEW) }
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(
modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
Column {
AutoDownloadPolicy.entries.forEach { item ->
Row(Modifier
@ -817,12 +557,7 @@ class FeedSettingsFragment : Fragment() {
}
}
)
Text(
text = stringResource(item.resId),
style = MaterialTheme.typography.bodyLarge.merge(),
// color = textColor,
modifier = Modifier.padding(start = 16.dp)
)
Text(text = stringResource(item.resId), style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
}
}
}
@ -836,17 +571,10 @@ class FeedSettingsFragment : Fragment() {
fun SetEpisodesCacheDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) {
Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
var newCache by remember { mutableStateOf((feed?.preferences?.autoDLMaxEpisodes ?: 1).toString()) }
TextField(value = newCache,
onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) newCache = it },
TextField(value = newCache, onValueChange = { if (it.isEmpty() || it.toIntOrNull() != null) newCache = it },
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
// visualTransformation = PositiveIntegerTransform(),
label = { Text("Max episodes allowed") }
@ -870,18 +598,10 @@ class FeedSettingsFragment : Fragment() {
var selected by remember {mutableStateOf(selectedOption)}
if (showDialog) {
Dialog(onDismissRequest = { onDismissRequest() }) {
Card(modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
queueSettingOptions.forEach { option ->
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
Checkbox(checked = option == selected,
onCheckedChange = { isChecked ->
selected = option
@ -911,6 +631,7 @@ class FeedSettingsFragment : Fragment() {
}
if (selected == "Custom") {
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 ->
Logd(TAG, "Queue selected: $name")
val q = queues?.firstOrNull { it.name == name }
@ -929,26 +650,14 @@ class FeedSettingsFragment : Fragment() {
fun AuthenticationDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) {
Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
val oldName = feed?.preferences?.username?:""
var newName by remember { mutableStateOf(oldName) }
TextField(value = newName,
onValueChange = { newName = it },
label = { Text("Username") }
)
TextField(value = newName, onValueChange = { newName = it }, label = { Text("Username") })
val oldPW = feed?.preferences?.password?:""
var newPW by remember { mutableStateOf(oldPW) }
TextField(value = newPW,
onValueChange = { newPW = it },
label = { Text("Password") }
)
TextField(value = newPW, onValueChange = { newPW = it }, label = { Text("Password") })
Button(onClick = {
if (newName.isNotEmpty() && oldName != newName) {
feed = upsertBlk(feed!!) {
@ -971,14 +680,8 @@ class FeedSettingsFragment : Fragment() {
fun AutoSkipDialog(showDialog: Boolean, onDismiss: () -> Unit) {
if (showDialog) {
Dialog(onDismissRequest = onDismiss) {
Card(modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
var intro by remember { mutableStateOf((feed?.preferences?.introSkip ?: 0).toString()) }
TextField(value = intro,
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.ui.activity.ShareReceiverActivity.Companion.receiveShared
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.FlowEvent
import ac.mdiq.podcini.util.Logd
@ -43,6 +44,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import io.realm.kotlin.query.Sort
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -82,12 +84,13 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val showDialog = remember { mutableStateOf(false) }
val dialogParam = remember { mutableStateOf(ShareLog()) }
if (showDialog.value) {
DetailDialog(
status = dialogParam.value,
showDialog = showDialog.value,
onDismissRequest = { showDialog.value = false },
)
DetailDialog(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),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(logs) { position, log ->
@ -96,7 +99,12 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (log.status == 1) {
showDialog.value = true
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 {
Row {
@ -104,6 +112,13 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val iconColor = remember { if (log.status == 1) Color.Green else Color.Yellow }
Icon(icon, "Info", tint = iconColor, modifier = Modifier.padding(end = 2.dp))
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)
val statusText = remember {"" }
@ -113,15 +128,6 @@ class SharedLogFragment : Fragment(), Toolbar.OnMenuItemClickListener {
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 {
val result = withContext(Dispatchers.IO) {
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)
}
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 {
FeedAutoDeleteOptions.forEach { text ->
Row(
Modifier.fillMaxWidth().padding(horizontal = 16.dp).selectable(
selected = (text == selectedOption),
Row(Modifier.fillMaxWidth().padding(horizontal = 16.dp)
.selectable(selected = (text == selectedOption),
onClick = {
if (text != selectedOption) {
val autoDeleteAction: AutoDeleteAction = AutoDeleteAction.fromTag(text)
@ -545,11 +544,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(selected = (text == selectedOption), onClick = { })
Text(
text = text,
style = MaterialTheme.typography.bodyLarge.merge(),
modifier = Modifier.padding(start = 16.dp)
)
Text(text = text, style = MaterialTheme.typography.bodyLarge.merge(), modifier = Modifier.padding(start = 16.dp))
}
}
}
@ -641,27 +636,15 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
CustomTheme(activity) {
if (showDialog.value) {
Dialog(onDismissRequest = { showDialog.value = false }) {
Card(
modifier = Modifier
.wrapContentSize(align = Alignment.Center)
.padding(16.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(16.dp), shape = RoundedCornerShape(16.dp)) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.Center) {
Row(Modifier.fillMaxWidth()) {
Icon(ImageVector.vectorResource(id = R.drawable.ic_refresh), "")
Spacer(modifier = Modifier.width(20.dp))
Text(
text = stringResource(R.string.keep_updated),
style = MaterialTheme.typography.titleLarge
)
Text(text = stringResource(R.string.keep_updated), style = MaterialTheme.typography.titleLarge)
Spacer(modifier = Modifier.weight(1f))
var checked by remember { mutableStateOf(false) }
Switch(
checked = checked,
Switch(checked = checked,
onCheckedChange = {
checked = it
saveFeedPreferences { pref: FeedPreferences ->
@ -670,10 +653,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
)
}
Text(
text = stringResource(R.string.keep_updated_summary),
style = MaterialTheme.typography.bodyMedium
)
Text(text = stringResource(R.string.keep_updated_summary), style = MaterialTheme.typography.bodyMedium)
}
}
}
@ -799,9 +779,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val scrollState = rememberScrollState()
Column(modifier = modifier.verticalScroll(scrollState), verticalArrangement = Arrangement.Bottom) {
if (isExpanded) options.forEachIndexed { _, button ->
FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp),
containerColor = Color.LightGray,
onClick = {}) { button() }
FloatingActionButton(modifier = Modifier.padding(start = 4.dp, bottom = 6.dp).height(40.dp), containerColor = Color.LightGray, onClick = {}) { button() }
}
FloatingActionButton(containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.secondary,
@ -826,10 +804,8 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}) {
if (if (useGrid == null) useGridLayout else useGrid!!) {
val lazyGridState = rememberLazyGridState()
LazyVerticalGrid(state = lazyGridState,
columns = GridCells.Fixed(3),
verticalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
LazyVerticalGrid(state = lazyGridState, columns = GridCells.Fixed(3),
verticalArrangement = Arrangement.spacedBy(16.dp), horizontalArrangement = Arrangement.spacedBy(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 ->
@ -867,8 +843,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val textColor = MaterialTheme.colorScheme.onSurface
ConstraintLayout {
val (coverImage, episodeCount, error) = createRefs()
AsyncImage(model = feed.imageUrl, contentDescription = "coverImage",
placeholder = painterResource(R.mipmap.ic_launcher),
AsyncImage(model = feed.imageUrl, contentDescription = "coverImage", placeholder = painterResource(R.mipmap.ic_launcher),
modifier = Modifier
.constrainAs(coverImage) {
top.linkTo(parent.top)
@ -881,24 +856,19 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
top.linkTo(coverImage.top)
})
// TODO: need to use state
if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red,
contentDescription = "error",
if (feed.lastUpdateFailed) Icon(painter = painterResource(R.drawable.ic_error), tint = Color.Red, contentDescription = "error",
modifier = Modifier.constrainAs(error) {
end.linkTo(parent.end)
bottom.linkTo(coverImage.bottom)
})
}
Text(feed.title ?: "No title",
color = textColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis)
Text(feed.title ?: "No title", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
}
}
}
} else {
val lazyListState = rememberLazyListState()
LazyColumn(state = lazyListState,
modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp),
LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)) {
itemsIndexed(feedListFiltered, key = {index, feed -> feed.id}) { index, feed ->
var isSelected by remember { mutableStateOf(false) }
@ -912,8 +882,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "toggleSelected: selected: ${selected.size}")
}
Row(Modifier.background(if (isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover",
placeholder = painterResource(R.mipmap.ic_launcher),
AsyncImage(model = feed.imageUrl, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher),
modifier = Modifier.width(80.dp).height(80.dp)
.clickable(onClick = {
Logd(TAG, "icon clicked!")
@ -964,13 +933,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
if (selectMode) {
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp)
.background(Color.LightGray),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically) {
Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24),
tint = Color.Black,
contentDescription = null,
Row(modifier = Modifier.align(Alignment.TopEnd).width(150.dp).height(45.dp).background(Color.LightGray),
horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
Icon(painter = painterResource(R.drawable.baseline_arrow_upward_24), tint = Color.Black, contentDescription = null,
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = {
selected.clear()
@ -980,9 +945,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
selectedSize = selected.size
Logd(TAG, "selectedIds: ${selected.size}")
}))
Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24),
tint = Color.Black,
contentDescription = null,
Icon(painter = painterResource(R.drawable.baseline_arrow_downward_24), tint = Color.Black, contentDescription = null,
modifier = Modifier.width(35.dp).height(35.dp).padding(end = 10.dp)
.clickable(onClick = {
selected.clear()
@ -993,9 +956,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Logd(TAG, "selectedIds: ${selected.size}")
}))
var selectAllRes by remember { mutableIntStateOf(R.drawable.ic_select_all) }
Icon(painter = painterResource(selectAllRes),
tint = Color.Black,
contentDescription = null,
Icon(painter = painterResource(selectAllRes), tint = Color.Black, contentDescription = null,
modifier = Modifier.width(35.dp).height(35.dp)
.clickable(onClick = {
if (selectedSize != feedListFiltered.size) {

View File

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

View File

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

View File

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

View File

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

View File

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