6.11.4 commit

This commit is contained in:
Xilin Jia 2024-10-17 14:42:23 +01:00
parent a38d7766fc
commit ac412c3906
25 changed files with 139 additions and 369 deletions

View File

@ -26,7 +26,7 @@ Apache License 2.0
[com.mikepenz:iconics-core](https://github.com/mikepenz/Android-Iconics/blob/develop/LICENSE) Apache License 2.0
[com.leinardi.android](https://github.com/leinardi/FloatingActionButtonSpeedDial/blob/release/LICENSE) Apache License 2.0
[//]: # ([com.leinardi.android](https://github.com/leinardi/FloatingActionButtonSpeedDial/blob/release/LICENSE) Apache License 2.0)
[com.github.ByteHamster](https://github.com/ByteHamster/SearchPreference/blob/master/LICENSE) MIT License

View File

@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 3020273
versionName "6.11.3"
versionCode 3020274
versionName "6.11.4"
applicationId "ac.mdiq.podcini.R"
def commit = ""
@ -240,7 +240,7 @@ dependencies {
implementation libs.google.material.typeface.outlined
implementation libs.fontawesome.typeface
implementation libs.speed.dial
// implementation libs.speed.dial
implementation libs.searchpreference
implementation libs.balloon
implementation libs.recyclerviewswipedecorator

View File

@ -440,13 +440,11 @@ class TTSActionButton(item: Episode) : EpisodeActionButton(item) {
j++
Logd(TAG, "onDone ${mediaFile.length()} $utteranceId")
}
@Deprecated("Deprecated in Java")
override fun onError(utteranceId: String) {
Log.e(TAG, "onError utterance error: $utteranceId")
Log.e(TAG, "onError $readerText")
}
override fun onError(utteranceId: String, errorCode: Int) {
Log.e(TAG, "onError1 utterance error: $utteranceId $errorCode")
Log.e(TAG, "onError1 $readerText")
@ -504,11 +502,7 @@ class TTSActionButton(item: Episode) : EpisodeActionButton(item) {
f.delete()
}
FeedEpisodesFragment.ttsWorking = false
} else withContext(Dispatchers.Main) {
Toast.makeText(context,
R.string.episode_has_no_content,
Toast.LENGTH_LONG).show()
}
} else withContext(Dispatchers.Main) { Toast.makeText(context, R.string.episode_has_no_content, Toast.LENGTH_LONG).show() }
item.setPlayed(false)
processing = 1f

View File

@ -600,8 +600,8 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String)
}
showPickerDialog = false
}) {
Icon(painter = painterResource(keys[index].getActionIcon()), tint = Color.Black, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp))
Text(keys[index].getTitle(context), textAlign = TextAlign.Center)
Icon(painter = painterResource(keys[index].getActionIcon()), tint = textColor, contentDescription = null, modifier = Modifier.width(35.dp).height(35.dp))
Text(keys[index].getTitle(context), color = textColor, textAlign = TextAlign.Center)
}
}
}

View File

@ -29,7 +29,6 @@ import ac.mdiq.podcini.ui.dialog.RatingDialog
import ac.mdiq.podcini.ui.fragment.*
import ac.mdiq.podcini.ui.fragment.AudioPlayerFragment.Companion.media3Controller
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.utils.LockableBottomSheetBehavior
import ac.mdiq.podcini.ui.utils.ThemeUtils.getDrawableFromAttr
import ac.mdiq.podcini.ui.utils.TransitionEffect
import ac.mdiq.podcini.util.EventFlow
@ -57,7 +56,6 @@ import android.view.ViewGroup.MarginLayoutParams
import android.widget.EditText
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.compose.ui.platform.ComposeView
import androidx.core.graphics.Insets
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
@ -99,7 +97,7 @@ class MainActivity : CastEnabledActivity() {
private lateinit var navDrawer: View
private lateinit var dummyView : View
private lateinit var controllerFuture: ListenableFuture<MediaController>
lateinit var bottomSheet: LockableBottomSheetBehavior<*>
lateinit var bottomSheet: BottomSheetBehavior<*>
private set
private var drawerToggle: ActionBarDrawerToggle? = null
@ -239,10 +237,10 @@ class MainActivity : CastEnabledActivity() {
runOnIOScope { checkFirstLaunch() }
this.bottomSheet = BottomSheetBehavior.from(audioPlayerView) as LockableBottomSheetBehavior<*>
this.bottomSheet = BottomSheetBehavior.from(audioPlayerView)
this.bottomSheet.isHideable = false
this.bottomSheet.isDraggable = false
this.bottomSheet.setBottomSheetCallback(bottomSheetCallback)
this.bottomSheet.addBottomSheetCallback(bottomSheetCallback)
restartUpdateAlarm(this, false)
runOnIOScope { SynchronizationQueueSink.syncNowIfNotSyncedRecently() }
@ -362,6 +360,7 @@ class MainActivity : CastEnabledActivity() {
// WorkManager.getInstance(this).pruneWork()
_binding = null
// realm.close()
bottomSheet.removeBottomSheetCallback(bottomSheetCallback)
drawerLayout?.removeDrawerListener(drawerToggle!!)
MediaController.releaseFuture(controllerFuture)
super.onDestroy()
@ -380,6 +379,7 @@ class MainActivity : CastEnabledActivity() {
private fun updateInsets() {
setPlayerVisible(audioPlayerView.visibility == View.VISIBLE)
val playerHeight = resources.getDimension(R.dimen.external_player_height).toInt()
Logd(TAG, "playerHeight: $playerHeight ${navigationBarInsets.bottom}")
bottomSheet.peekHeight = playerHeight + navigationBarInsets.bottom
}
@ -387,20 +387,16 @@ class MainActivity : CastEnabledActivity() {
Logd(TAG, "setPlayerVisible $visible_")
val visible = visible_ ?: (bottomSheet.state != BottomSheetBehavior.STATE_COLLAPSED)
bottomSheet.setLocked(!visible)
// bottomSheet.setLocked(!visible)
if (visible) bottomSheetCallback.onStateChanged(dummyView, bottomSheet.state) // Update toolbar visibility
else bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
val params = mainView.layoutParams as MarginLayoutParams
val externalPlayerHeight = resources.getDimension(R.dimen.external_player_height).toInt()
Logd(TAG, "externalPlayerHeight: $externalPlayerHeight ${navigationBarInsets.bottom}")
params.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right,
navigationBarInsets.bottom + (if (visible) externalPlayerHeight else 0))
mainView.layoutParams = params
// val playerView = findViewById<FragmentContainerView>(R.id.playerFragment1)
// val playerView = findViewById<ComposeView>(R.id.player1)
// val playerParams = playerView?.layoutParams as? MarginLayoutParams
// playerParams?.setMargins(navigationBarInsets.left, 0, navigationBarInsets.right, 0)
// playerView?.layoutParams = playerParams
audioPlayerView.visibility = if (visible) View.VISIBLE else View.GONE
}

View File

@ -81,6 +81,7 @@ import androidx.compose.ui.window.Dialog
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.documentfile.provider.DocumentFile
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import io.realm.kotlin.notifications.SingleQueryChange
import io.realm.kotlin.notifications.UpdatedObject
import kotlinx.coroutines.*
@ -602,10 +603,10 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
val (imgvCover, checkMark) = createRefs()
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(vm.episode)
Logd(TAG, "imgLoc: $imgLoc")
AsyncImage(model = imgLoc, contentDescription = "imgvCover",
placeholder = painterResource(R.mipmap.ic_launcher),
error = painterResource(R.mipmap.ic_launcher),
val painter = rememberAsyncImagePainter(model = imgLoc)
Image(
painter = painter,
contentDescription = "imgvCover",
modifier = Modifier.width(56.dp).height(56.dp)
.constrainAs(imgvCover) {
top.linkTo(parent.top)
@ -615,7 +616,8 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
Logd(TAG, "icon clicked!")
if (selectMode) toggleSelected()
else if (vm.episode.feed != null) activity.loadChildFragment(FeedInfoFragment.newInstance(vm.episode.feed!!))
}))
})
)
val alpha = if (vm.playedState) 1.0f else 0f
if (vm.playedState) Icon(painter = painterResource(R.drawable.ic_check), tint = textColor, contentDescription = "played_mark",
modifier = Modifier.background(Color.Green).alpha(alpha).constrainAs(checkMark) {
@ -645,7 +647,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
if (index>=vms.size) return@LaunchedEffect
vms[index].inQueueState = curQueue.contains(vms[index].episode)
}
val dur = vm.episode.media!!.getDuration()
val dur = vm.episode.media?.getDuration() ?: 0
val durText = DurationConverter.getDurationStringLong(dur)
Row {
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
@ -656,7 +658,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
if (vm.inQueueState)
Icon(painter = painterResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", modifier = Modifier.width(14.dp).height(14.dp))
val curContext = LocalContext.current
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " + if((vm.episode.media?.size?:0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media!!.size) else ""
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " + if((vm.episode.media?.size?:0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium)
}
Text(vm.episode.title?:"", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
@ -677,7 +679,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
if (actionButton_ == null) {
LaunchedEffect(vms[index].downloadState) {
if (index>=vms.size) return@LaunchedEffect
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media!!.downloadUrl!!) ?: 0
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media?.downloadUrl?:"") ?: 0
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
vm.actionRes = vm.actionButton!!.getDrawable()

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.database.Feeds
import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Feed.Companion.MAX_SYNTHETIC_ID
import ac.mdiq.podcini.storage.model.Rating
import ac.mdiq.podcini.storage.model.SubscriptionLog
import ac.mdiq.podcini.ui.activity.MainActivity
@ -88,12 +89,24 @@ fun RemoveFeedDialog(feeds: List<Feed>, onDismissRequest: () -> Unit, callback:
CoroutineScope(Dispatchers.IO).launch {
try {
for (f in feeds) {
val sLog = SubscriptionLog(f.id, f.title?:"", f.downloadUrl?:"", f.link?:"", SubscriptionLog.Type.Feed.name)
upsert(sLog) {
it.rating = f.rating
it.comment = f.comment
it.comment += "\nReason to remove:\n" + textState.text
it.cancelDate = Date().time
if (f.id > MAX_SYNTHETIC_ID) {
val sLog = SubscriptionLog(f.id, f.title ?: "", f.downloadUrl ?: "", f.link ?: "", SubscriptionLog.Type.Feed.name)
upsert(sLog) {
it.rating = f.rating
it.comment = f.comment
it.comment += "\nReason to remove:\n" + textState.text
it.cancelDate = Date().time
}
} else {
for (e in f.episodes) {
val sLog = SubscriptionLog(e.id, e.title ?: "", e.media?.downloadUrl ?: "", e.link ?: "", SubscriptionLog.Type.Media.name)
upsert(sLog) {
it.rating = e.rating
it.comment = e.comment
it.comment += "\nReason to remove:\n" + textState.text
it.cancelDate = Date().time
}
}
}
deleteFeedSync(context, f.id, false)
}

View File

@ -6,17 +6,12 @@ import ac.mdiq.podcini.databinding.FilterDialogRowBinding
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.TAG
import ac.mdiq.podcini.util.Logd
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButtonToggleGroup
@ -89,31 +84,12 @@ abstract class EpisodeFilterDialog : BottomSheetDialogFragment() {
return layout
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener { dialogInterface: DialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
setupFullHeight(bottomSheetDialog)
}
return dialog
}
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
super.onDestroyView()
}
private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
val bottomSheet = bottomSheetDialog.findViewById<View>(com.leinardi.android.speeddial.R.id.design_bottom_sheet) as? FrameLayout
if (bottomSheet != null) {
val behavior = BottomSheetBehavior.from(bottomSheet)
val layoutParams = bottomSheet.layoutParams
bottomSheet.layoutParams = layoutParams
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
abstract fun onFilterChanged(newFilterValues: Set<String>)
enum class FeedItemFilterGroup(vararg values: ItemProperties) {

View File

@ -7,17 +7,12 @@ import ac.mdiq.podcini.databinding.SortDialogItemBinding
import ac.mdiq.podcini.storage.model.EpisodeSortOrder
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.TAG
import ac.mdiq.podcini.util.Logd
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.CompoundButton
import android.widget.FrameLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
open class EpisodeSortDialog : BottomSheetDialogFragment() {
@ -91,28 +86,9 @@ open class EpisodeSortDialog : BottomSheetDialogFragment() {
protected open fun onSelectionChanged() {}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener { dialogInterface: DialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
setupFullHeight(bottomSheetDialog)
}
return dialog
}
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
super.onDestroyView()
}
private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
val bottomSheet = bottomSheetDialog.findViewById<FrameLayout>(com.leinardi.android.speeddial.R.id.design_bottom_sheet)
if (bottomSheet != null) {
val behavior = BottomSheetBehavior.from(bottomSheet)
val layoutParams = bottomSheet.layoutParams
bottomSheet.layoutParams = layoutParams
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
}

View File

@ -10,20 +10,15 @@ import ac.mdiq.podcini.storage.model.FeedSortOrder
import ac.mdiq.podcini.storage.model.FeedSortOrder.Companion.getSortOrder
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.feedOrderBy
import ac.mdiq.podcini.ui.fragment.SubscriptionsFragment.Companion.feedOrderDir
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.EventFlow
import ac.mdiq.podcini.util.FlowEvent
import android.app.Dialog
import android.content.DialogInterface
import ac.mdiq.podcini.util.Logd
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.widget.CompoundButton
import android.widget.FrameLayout
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
open class FeedSortDialog : BottomSheetDialogFragment() {
@ -110,31 +105,12 @@ open class FeedSortDialog : BottomSheetDialogFragment() {
protected open fun onSelectionChanged() {}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener { dialogInterface: DialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
setupFullHeight(bottomSheetDialog)
}
return dialog
}
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
super.onDestroyView()
}
private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
val bottomSheet = bottomSheetDialog.findViewById<FrameLayout>(com.leinardi.android.speeddial.R.id.design_bottom_sheet)
if (bottomSheet != null) {
val behavior = BottomSheetBehavior.from(bottomSheet)
val layoutParams = bottomSheet.layoutParams
bottomSheet.layoutParams = layoutParams
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
private fun setFeedOrder(selected: String, dir: Int) {
appPrefs.edit().putString(UserPreferences.Prefs.prefDrawerFeedOrder.name, selected).apply()
appPrefs.edit().putInt(UserPreferences.Prefs.prefDrawerFeedOrderDir.name, dir).apply()

View File

@ -6,7 +6,6 @@ import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.ServiceStatusHandler
import ac.mdiq.podcini.playback.base.InTheatre.curEpisode
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.VideoMode
@ -149,19 +148,10 @@ class AudioPlayerFragment : Fragment() {
val composeView = ComposeView(requireContext()).apply {
setContent {
CustomTheme(requireContext()) {
// Column(modifier = Modifier.fillMaxSize().statusBarsPadding().navigationBarsPadding() ) {
// if (isCollapsed) PlayerUI()
//// else Spacer(modifier = Modifier.size(0.dp))
// Toolbar()
// DetailUI(modifier = Modifier.weight(1f))
// if (!isCollapsed) PlayerUI()
//// else Spacer(modifier = Modifier.size(0.dp))
// }
Box(modifier = Modifier.fillMaxWidth().statusBarsPadding().navigationBarsPadding()) {
val aligm = if (isCollapsed) Alignment.TopCenter else Alignment.BottomCenter
PlayerUI(Modifier.align(aligm).zIndex(1f))
Box(modifier = Modifier.fillMaxWidth().then(if (isCollapsed) Modifier else Modifier.statusBarsPadding().navigationBarsPadding())) {
PlayerUI(Modifier.align(if (isCollapsed) Alignment.TopCenter else Alignment.BottomCenter).zIndex(1f))
if (!isCollapsed) {
Column(Modifier.padding(bottom = 90.dp)) {
Column(Modifier.padding(bottom = 120.dp)) {
Toolbar()
DetailUI(modifier = Modifier)
}
@ -269,14 +259,14 @@ class AudioPlayerFragment : Fragment() {
if (curMedia != null) {
val media = curMedia!!
setIsShowPlay(!isShowPlay)
if (media.getMediaType() == MediaType.VIDEO && MediaPlayerBase.status != PlayerStatus.PLAYING &&
if (media.getMediaType() == MediaType.VIDEO && status != PlayerStatus.PLAYING &&
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
playPause()
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
} else playPause()
}
}, onLongClick = {
if (controller != null && MediaPlayerBase.status == PlayerStatus.PLAYING) {
if (controller != null && status == PlayerStatus.PLAYING) {
val fallbackSpeed = UserPreferences.fallbackSpeed
if (fallbackSpeed > 0.1f) toggleFallbackSpeed(fallbackSpeed)
}
@ -307,7 +297,7 @@ class AudioPlayerFragment : Fragment() {
Icon(painter = painterResource(R.drawable.ic_skip_48dp), tint = textColor,
contentDescription = "rewind",
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
if (controller != null && MediaPlayerBase.status == PlayerStatus.PLAYING) {
if (controller != null && status == PlayerStatus.PLAYING) {
val speedForward = UserPreferences.speedforwardSpeed
if (speedForward > 0.1f) speedForward(speedForward)
}
@ -530,9 +520,10 @@ class AudioPlayerFragment : Fragment() {
onPositionUpdate(FlowEvent.PlaybackPositionEvent(media, media.getPosition(), media.getDuration()))
if (prevMedia?.getIdentifier() != media.getIdentifier()) imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
if (isPlayingVideoLocally && (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) {
(activity as MainActivity).bottomSheet.setLocked(true)
(activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_COLLAPSED)
} else (activity as MainActivity).bottomSheet.setLocked(false)
// (activity as MainActivity).bottomSheet.setLocked(true)
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
}
// else (activity as MainActivity).bottomSheet.setLocked(false)
prevMedia = media
}

View File

@ -32,8 +32,6 @@ 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.activity.VideoplayerActivity
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion
import ac.mdiq.podcini.ui.compose.ChaptersDialog
import ac.mdiq.podcini.ui.compose.ChooseRatingDialog
import ac.mdiq.podcini.ui.compose.CustomTheme
@ -193,8 +191,8 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val imgLoc = if (episode != null) ImageResourceUtils.getEpisodeListImageLocation(episode!!) else null
AsyncImage(model = imgLoc, contentDescription = "imgvCover", error = painterResource(R.mipmap.ic_launcher), modifier = Modifier.width(56.dp).height(56.dp).clickable(onClick = { openPodcast() }))
Column(modifier = Modifier.padding(start = 10.dp)) {
Text(txtvPodcast, color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.clickable { openPodcast() })
Text(txtvTitle, color = textColor, style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold), maxLines = 5, overflow = TextOverflow.Ellipsis)
Text(txtvPodcast, color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth().clickable { openPodcast() })
Text(txtvTitle, color = textColor, style = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Bold), modifier = Modifier.fillMaxWidth(), maxLines = 5, overflow = TextOverflow.Ellipsis)
Text("$txtvPublished · $txtvDuration · $txtvSize", color = textColor, style = MaterialTheme.typography.bodyMedium)
}
}

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.ui.fragment
//import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FeedItemListFragmentBinding
import ac.mdiq.podcini.net.download.DownloadStatus
@ -286,8 +285,8 @@ import java.util.concurrent.Semaphore
Column(Modifier.fillMaxWidth().constrainAs(taColumn) {
top.linkTo(imgvCover.top)
start.linkTo(imgvCover.end) }) {
Text(feed?.title?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(feed?.author?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(feed?.title?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(feed?.author?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
@ -504,7 +503,7 @@ import java.util.concurrent.Semaphore
// if (!event.isRunning) nextPageLoader.root.visibility = View.GONE
infoTextUpdate = if (event.isRunning) getString(R.string.refreshing_label) else ""
infoBarText.value = "$infoTextFiltered $infoTextUpdate"
if (event.isRunning == false) loadFeed()
if (!event.isRunning) loadFeed()
// binding.swipeRefresh.isRefreshing = event.isRunning
}

View File

@ -13,6 +13,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Feed.Companion.MAX_SYNTHETIC_ID
import ac.mdiq.podcini.storage.model.FeedFunding
import ac.mdiq.podcini.storage.model.Rating
import ac.mdiq.podcini.ui.activity.MainActivity
@ -210,8 +211,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Column(Modifier.constrainAs(taColumn) {
top.linkTo(imgvCover.top)
start.linkTo(imgvCover.end) }) {
Text(feed.title ?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(txtvAuthor, color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(feed.title ?:"", color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.fillMaxWidth(), maxLines = 2, overflow = TextOverflow.Ellipsis)
Text(text = txtvAuthor, color = textColor, fontWeight = FontWeight.Bold, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
}
@ -240,50 +241,52 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp).clickable { showEditComment = true })
Text(commentTextState.text, color = textColor, style = MaterialTheme.typography.bodyMedium, modifier = Modifier.padding(start = 15.dp, bottom = 10.dp))
Text(stringResource(R.string.url_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp))
Text(text = txtvUrl?:"", color = textColor, modifier = Modifier.clickable {
if (feed.downloadUrl != null) {
val url: String = feed.downloadUrl!!
val clipData: ClipData = ClipData.newPlainText(url, url)
val cm = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(clipData)
if (Build.VERSION.SDK_INT <= 32) (activity as MainActivity).showSnackbarAbovePlayer(R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT)
}
})
if (feed.paymentLinks.isNotEmpty()) {
Text(stringResource(R.string.support_funding_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp))
fun fundingText(): String {
val fundingList: ArrayList<FeedFunding> = feed.paymentLinks
// Filter for duplicates, but keep items in the order that they have in the feed.
val i: MutableIterator<FeedFunding> = fundingList.iterator()
while (i.hasNext()) {
val funding: FeedFunding = i.next()
for (other in fundingList) {
if (other.url == funding.url) {
if (other.content != null && funding.content != null && other.content!!.length > funding.content!!.length) {
i.remove()
break
if (feed.id > MAX_SYNTHETIC_ID) {
Text(stringResource(R.string.url_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp))
Text(text = txtvUrl ?: "", color = textColor, modifier = Modifier.clickable {
if (feed.downloadUrl != null) {
val url: String = feed.downloadUrl!!
val clipData: ClipData = ClipData.newPlainText(url, url)
val cm = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(clipData)
if (Build.VERSION.SDK_INT <= 32) (activity as MainActivity).showSnackbarAbovePlayer(R.string.copied_to_clipboard, Snackbar.LENGTH_SHORT)
}
})
if (feed.paymentLinks.isNotEmpty()) {
Text(stringResource(R.string.support_funding_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp))
fun fundingText(): String {
val fundingList: ArrayList<FeedFunding> = feed.paymentLinks
// Filter for duplicates, but keep items in the order that they have in the feed.
val i: MutableIterator<FeedFunding> = fundingList.iterator()
while (i.hasNext()) {
val funding: FeedFunding = i.next()
for (other in fundingList) {
if (other.url == funding.url) {
if (other.content != null && funding.content != null && other.content!!.length > funding.content!!.length) {
i.remove()
break
}
}
}
}
val str = StringBuilder()
for (funding in fundingList) {
str.append(if (funding.content == null || funding.content!!.isEmpty()) requireContext().resources.getString(
R.string.support_podcast)
else funding.content).append(" ").append(funding.url)
str.append("\n")
}
return StringBuilder(StringUtils.trim(str.toString())).toString()
}
val str = StringBuilder()
for (funding in fundingList) {
str.append(if (funding.content == null || funding.content!!.isEmpty()) requireContext().resources.getString(
R.string.support_podcast)
else funding.content).append(" ").append(funding.url)
str.append("\n")
}
return StringBuilder(StringUtils.trim(str.toString())).toString()
val fundText = remember { fundingText() }
Text(fundText, color = textColor)
}
Button(modifier = Modifier.padding(top = 10.dp), onClick = {
val fragment = SearchResultsFragment.newInstance(CombinedSearcher::class.java, "$txtvAuthor podcasts")
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}) {
Text(stringResource(R.string.feeds_related_to_author))
}
val fundText = remember { fundingText() }
Text(fundText, color = textColor)
}
Button(modifier = Modifier.padding(top = 10.dp), onClick = {
val fragment = SearchResultsFragment.newInstance(CombinedSearcher::class.java, "$txtvAuthor podcasts")
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}) {
Text(stringResource(R.string.feeds_related_to_author))
}
Text(stringResource(R.string.statistics_label), color = textColor, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(top = 16.dp, bottom = 4.dp))
val arguments = Bundle()

View File

@ -418,11 +418,11 @@ class OnlineFeedFragment : Fragment() {
Text(HtmlToPlainText.getPlainText(feed?.description ?: ""), color = textColor, style = MaterialTheme.typography.bodyMedium)
val sLog = remember {feedLogsMap_[feed?.downloadUrl?:""] }
if (sLog != null) {
val commentTextState by remember { mutableStateOf(TextFieldValue(sLog?.comment ?: "")) }
val commentTextState by remember { mutableStateOf(TextFieldValue(sLog.comment ?: "")) }
val context = LocalContext.current
val cancelDate = remember { formatAbbrev(context, Date(sLog.cancelDate)) }
val ratingRes = remember { fromCode(sLog.rating).res }
if (!commentTextState.text.isEmpty()) {
if (commentTextState.text.isNotEmpty()) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 15.dp, top = 10.dp, bottom = 5.dp)) {
Text(stringResource(R.string.my_opinion_label), color = MaterialTheme.colorScheme.primary, style = MaterialTheme.typography.titleMedium)
Icon(painter = painterResource(ratingRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = null, modifier = Modifier.padding(start = 5.dp))

View File

@ -181,7 +181,7 @@ import kotlin.math.max
swipeActions = SwipeActions(this, TAG)
swipeActions.setFilter(EpisodeFilter(EpisodeFilter.States.queued.name))
swipeActionsBin = SwipeActions(this, TAG+".Bin")
swipeActionsBin = SwipeActions(this, "$TAG.Bin")
swipeActionsBin.setFilter(EpisodeFilter(EpisodeFilter.States.queued.name))
binding.lazyColumn.setContent {
@ -462,6 +462,7 @@ import kotlin.math.max
toolbar.addView(spinnerLayout)
}
refreshMenuItems()
refreshSwipeTelltale()
if (showBin) {
item.setIcon(R.drawable.playlist_play)
// speedDialView.addActionItem(addToQueueActionItem)

View File

@ -1160,31 +1160,12 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return layout
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.setOnShowListener { dialogInterface: DialogInterface ->
val bottomSheetDialog = dialogInterface as BottomSheetDialog
setupFullHeight(bottomSheetDialog)
}
return dialog
}
override fun onDestroyView() {
Logd(TAG, "onDestroyView")
_binding = null
super.onDestroyView()
}
private fun setupFullHeight(bottomSheetDialog: BottomSheetDialog) {
val bottomSheet = bottomSheetDialog.findViewById<View>(com.leinardi.android.speeddial.R.id.design_bottom_sheet) as? FrameLayout
if (bottomSheet != null) {
val behavior = BottomSheetBehavior.from(bottomSheet)
val layoutParams = bottomSheet.layoutParams
bottomSheet.layoutParams = layoutParams
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
private fun onFilterChanged(newFilterValues: Set<String>) {
feedsFilter = StringUtils.join(newFilterValues, ",")
Logd(TAG, "onFilterChanged: $feedsFilter")

View File

@ -1,63 +0,0 @@
package ac.mdiq.podcini.ui.utils
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.bottomsheet.ViewPagerBottomSheetBehavior
/**
* Based on https://stackoverflow.com/a/40798214
*/
class LockableBottomSheetBehavior<V : View?> : ViewPagerBottomSheetBehavior<V> {
private var isLocked = false
constructor()
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
fun setLocked(locked: Boolean) {
isLocked = locked
}
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V & Any, event: MotionEvent): Boolean {
var handled = false
if (!isLocked) handled = super.onInterceptTouchEvent(parent, child, event)
return handled
}
override fun onTouchEvent(parent: CoordinatorLayout, child: V & Any, event: MotionEvent): Boolean {
var handled = false
if (!isLocked) handled = super.onTouchEvent(parent, child, event)
return handled
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V & Any, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
var handled = false
if (!isLocked) handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
return handled
}
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V & Any, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (!isLocked) super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
}
override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V & Any, target: View, type: Int) {
if (!isLocked) super.onStopNestedScroll(coordinatorLayout, child, target, type)
}
override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: V & Any, target: View, velocityX: Float, velocityY: Float): Boolean {
var handled = false
if (!isLocked) handled = super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
return handled
}
}

View File

@ -1,55 +0,0 @@
package com.google.android.material.bottomsheet
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import java.lang.ref.WeakReference
/**
* Override [.findScrollingChild] to support [ViewPager]'s nested scrolling.
* In order to override package level method and field.
* This class put in the same package path where [BottomSheetBehavior] located.
* Source: https://medium.com/@hanru.yeh/funny-solution-that-makes-bottomsheetdialog-support-viewpager-with-nestedscrollingchilds-bfdca72235c3
*/
open class ViewPagerBottomSheetBehavior<V : View?> : BottomSheetBehavior<V> {
constructor() : super()
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
public override fun findScrollingChild(view: View): View? {
if (view.isNestedScrollingEnabled) {
return view
}
when (view) {
is ViewPager2 -> {
val recycler = view.getChildAt(0) as RecyclerView
val currentViewPagerChild = recycler.getChildAt(view.currentItem)
if (currentViewPagerChild != null) {
return findScrollingChild(currentViewPagerChild)
}
}
is ViewGroup -> {
var i = 0
val count = view.childCount
while (i < count) {
val scrollingChild = findScrollingChild(view.getChildAt(i))
if (scrollingChild != null) {
return scrollingChild
}
i++
}
}
}
return null
}
fun updateScrollingChild() {
val childView = viewRef?.get() ?: return
val scrollingChild = findScrollingChild(childView)
nestedScrollingChildRef = WeakReference(scrollingChild)
}
}

View File

@ -30,7 +30,7 @@
android:background="?android:attr/colorBackground"
android:elevation="8dp"
android:visibility="gone"
app:layout_behavior="ac.mdiq.podcini.ui.utils.LockableBottomSheetBehavior" />
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,41 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.leinardi.android.speeddial.SpeedDialOverlayLayout
android:id="@+id/fabSDOverlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:importantForAccessibility="no" />
<!-- The FAB SpeedDial
1. MUST be placed at the bottom of the layout xml to ensure it is at the front,
clickable on Pre-Lollipop devices (that do not support elevation).
See: https://stackoverflow.com/a/2614402
2. ScrollView is needed to ensure the vertical list of speed dials are
accessible when screen height is small, eg., landscape mode on most phones.
-->
<ScrollView
android:id="@+id/fabSDScrollCtr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:elevation="@dimen/sd_open_elevation">
<com.leinardi.android.speeddial.SpeedDialView
android:id="@+id/fabSD"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:accessibilityTraversalBefore="@android:id/list"
android:contentDescription="@string/apply_action"
android:visibility="gone"
app:sdMainFabClosedSrc="@drawable/ic_fab_edit"
app:sdOverlayLayout="@id/fabSDOverlay" />
</ScrollView>
</merge>

View File

@ -6,7 +6,7 @@
<dimen name="drawer_corner_size">16dp</dimen>
<dimen name="widget_margin">0dp</dimen>
<dimen name="widget_inner_radius">4dp</dimen>
<dimen name="external_player_height">135dp</dimen>
<dimen name="external_player_height">110dp</dimen>
<dimen name="text_size_micro">12sp</dimen>
<dimen name="text_size_small">14sp</dimen>
<dimen name="text_size_navdrawer">16sp</dimen>

View File

@ -1,3 +1,15 @@
# 6.11.4
* corrected color contrast in SwipeActions dialog
* removed the empty space on top of playerUI
* largely improved scroll performance of episodes list caused by image loading
* fixed title text out of screen issue in some headers
* fixed swipe actions not initialized in Queue Bin
* in FeedInfo details, removed inapplicable items for synthetic feeds
* when removing synthetic feed, record all episodes in the feed in SubscriptionLog
* updated some Compose dependencies
* speed-dial dependency removed and old bottomSheet multi-select codes cleaned up
# 6.11.3
* supports Youtube live episodes received from share

View File

@ -0,0 +1,11 @@
Version 6.11.4
* corrected color contrast in SwipeActions dialog
* removed the empty space on top of playerUI
* largely improved scroll performance of episodes list caused by image loading
* fixed title text out of screen issue in some headers
* fixed swipe actions not initialized in Queue Bin
* in FeedInfo details, removed inapplicable items for synthetic feeds
* when removing synthetic feed, record all episodes in the feed in SubscriptionLog
* updated some Compose dependencies
* speed-dial dependency removed and old bottomSheet multi-select codes cleaned up

View File

@ -1,14 +1,14 @@
[versions]
activityCompose = "1.9.2"
annotation = "1.8.2"
activityCompose = "1.9.3"
annotation = "1.9.0"
appcompat = "1.7.0"
awaitility = "4.2.1"
balloon = "1.6.6"
coil = "2.7.0"
commonsLang3 = "3.15.0"
commonsIo = "2.16.1"
composeBom = "2024.09.03"
composeBom = "2024.10.00"
conscryptAndroid = "2.5.2"
constraintlayoutCompose = "1.0.1"
coordinatorlayout = "1.2.0"
@ -63,11 +63,11 @@ runner = "1.6.2"
rxandroid = "3.0.2"
rxjava = "2.2.21"
rxjavaVersion = "3.1.8"
speedDial = "3.3.0"
#speedDial = "3.3.0"
searchpreference = "v2.5.0"
stream = "1.2.2"
uiToolingPreview = "1.7.3"
uiTooling = "1.7.3"
uiToolingPreview = "1.7.4"
uiTooling = "1.7.4"
viewpager2 = "1.1.0"
vistaguide = "lv0.24.2.6"
wearable = "2.9.0"
@ -151,7 +151,7 @@ rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroi
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjavaVersion" }
rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }
searchpreference = { module = "com.github.ByteHamster:SearchPreference", version.ref = "searchpreference" }
speed-dial = { module = "com.leinardi.android:speed-dial", version.ref = "speedDial" }
#speed-dial = { module = "com.leinardi.android:speed-dial", version.ref = "speedDial" }
stream = { module = "com.annimon:stream", version.ref = "stream" }
vistaguide = { module = "com.github.XilinJia.vistaguide:VistaGuide", version.ref = "vistaguide" }
desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" }