6.11.5 commit
This commit is contained in:
parent
ac412c3906
commit
ca6afe3c27
|
@ -16,7 +16,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
|
||||||
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
|
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
|
||||||
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
|
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
|
||||||
#### If you need to cast to an external speaker, you should install the "play" apk, not the "free" apk, that's about the difference between the two.
|
#### If you need to cast to an external speaker, you should install the "play" apk, not the "free" apk, that's about the difference between the two.
|
||||||
#### Since version 6.8.5, Podcini.R is built to target SDK 30 (Android 11), though built with SDK 35 and tested on Android 14. This is to counter 2-year old Google issue ForegroundServiceStartNotAllowedException. For more see [this issue](https://github.com/XilinJia/Podcini/issues/88)
|
#### Since version 6.11.5, Podcini.R is back to be built to target SDK 35 (Android 15), but requests for permission for unrestricted background activities for uninterrupted background play of a playlist. For more see [this issue](https://github.com/XilinJia/Podcini/issues/88)
|
||||||
#### If you are migrating from Podcini version 5, please read the migrationTo5.md file for migration instructions.
|
#### If you are migrating from Podcini version 5, please read the migrationTo5.md file for migration instructions.
|
||||||
|
|
||||||
This project was developed from a fork of [AntennaPod](<https://github.com/AntennaPod/AntennaPod>) as of Feb 5 2024.
|
This project was developed from a fork of [AntennaPod](<https://github.com/AntennaPod/AntennaPod>) as of Feb 5 2024.
|
||||||
|
|
|
@ -20,7 +20,7 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk 24
|
minSdk 24
|
||||||
compileSdk 35
|
compileSdk 35
|
||||||
targetSdk 30
|
targetSdk 35
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '17'
|
jvmTarget = '17'
|
||||||
|
@ -31,8 +31,8 @@ android {
|
||||||
testApplicationId "ac.mdiq.podcini.tests"
|
testApplicationId "ac.mdiq.podcini.tests"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
versionCode 3020274
|
versionCode 3020275
|
||||||
versionName "6.11.4"
|
versionName "6.11.5"
|
||||||
|
|
||||||
applicationId "ac.mdiq.podcini.R"
|
applicationId "ac.mdiq.podcini.R"
|
||||||
def commit = ""
|
def commit = ""
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE" />
|
<uses-permission android:name="android.permission.VIBRATE" />
|
||||||
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||||
|
|
||||||
<supports-screens
|
<supports-screens
|
||||||
android:anyDensity="true"
|
android:anyDensity="true"
|
||||||
|
|
|
@ -441,10 +441,10 @@ object Feeds {
|
||||||
return feed
|
return feed
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addToYoutubeSyndicate(episode: Episode, video: Boolean) {
|
fun addToYoutubeSyndicate(episode: Episode, video: Boolean) : Int {
|
||||||
val feed = getYoutubeSyndicate(video, episode.media?.downloadUrl?.contains("music") == true)
|
val feed = getYoutubeSyndicate(video, episode.media?.downloadUrl?.contains("music") == true)
|
||||||
Logd(TAG, "addToYoutubeSyndicate: feed: ${feed.title}")
|
Logd(TAG, "addToYoutubeSyndicate: feed: ${feed.title}")
|
||||||
if (searchEpisodeByIdentifyingValue(feed.episodes, episode) != null) return
|
if (searchEpisodeByIdentifyingValue(feed.episodes, episode) != null) return 2
|
||||||
|
|
||||||
Logd(TAG, "addToYoutubeSyndicate adding new episode: ${episode.title}")
|
Logd(TAG, "addToYoutubeSyndicate adding new episode: ${episode.title}")
|
||||||
episode.feed = feed
|
episode.feed = feed
|
||||||
|
@ -455,6 +455,7 @@ object Feeds {
|
||||||
feed.episodes.add(episode)
|
feed.episodes.add(episode)
|
||||||
upsertBlk(feed) {}
|
upsertBlk(feed) {}
|
||||||
EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
|
EventFlow.postStickyEvent(FlowEvent.FeedUpdatingEvent(false))
|
||||||
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSynthetic(feedId: Long, name: String): Feed {
|
fun createSynthetic(feedId: Long, name: String): Feed {
|
||||||
|
|
|
@ -12,7 +12,7 @@ class ShareLog : RealmObject {
|
||||||
|
|
||||||
var type: String? = null
|
var type: String? = null
|
||||||
|
|
||||||
var status: Int = 0
|
var status: Int = Status.ERROR.ordinal
|
||||||
|
|
||||||
var details: String = ""
|
var details: String = ""
|
||||||
|
|
||||||
|
@ -22,4 +22,10 @@ class ShareLog : RealmObject {
|
||||||
id = Date().time
|
id = Date().time
|
||||||
this.url = url
|
this.url = url
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
ERROR,
|
||||||
|
SUCCESS,
|
||||||
|
EXISTING
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -36,6 +36,7 @@ import ac.mdiq.podcini.util.FlowEvent
|
||||||
import ac.mdiq.podcini.util.Logd
|
import ac.mdiq.podcini.util.Logd
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.AppOpsManager
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
@ -46,7 +47,9 @@ import android.media.AudioManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.PowerManager
|
||||||
import android.os.StrictMode
|
import android.os.StrictMode
|
||||||
|
import android.provider.Settings
|
||||||
import android.util.DisplayMetrics
|
import android.util.DisplayMetrics
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
|
@ -54,8 +57,11 @@ import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.ActionBarDrawerToggle
|
import androidx.appcompat.app.ActionBarDrawerToggle
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
@ -107,16 +113,70 @@ class MainActivity : CastEnabledActivity() {
|
||||||
private var lastTheme = 0
|
private var lastTheme = 0
|
||||||
private var navigationBarInsets = Insets.NONE
|
private var navigationBarInsets = Insets.NONE
|
||||||
|
|
||||||
|
// private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
||||||
|
// if (isGranted) return@registerForActivityResult
|
||||||
|
// MaterialAlertDialogBuilder(this)
|
||||||
|
// .setMessage(R.string.notification_permission_text)
|
||||||
|
// .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> }
|
||||||
|
// .setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int -> finish() }
|
||||||
|
// .show()
|
||||||
|
// }
|
||||||
|
|
||||||
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
|
||||||
if (isGranted) return@registerForActivityResult
|
Toast.makeText(this, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
|
||||||
|
//
|
||||||
|
// if (isGranted) return@registerForActivityResult
|
||||||
|
|
||||||
|
// MaterialAlertDialogBuilder(this)
|
||||||
|
// .setMessage(R.string.notification_permission_text)
|
||||||
|
// .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
// }
|
||||||
|
// .setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int ->
|
||||||
|
// }
|
||||||
|
// .show()
|
||||||
|
|
||||||
|
if (isGranted) {
|
||||||
|
checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||||
|
return@registerForActivityResult
|
||||||
|
}
|
||||||
|
// checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(R.string.notification_permission_text)
|
.setMessage(R.string.notification_permission_text)
|
||||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> }
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int -> finish() }
|
checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int ->
|
||||||
|
checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||||
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun checkAndRequestUnrestrictedBackgroundActivity(context: Context) {
|
||||||
|
val powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
||||||
|
val isIgnoringBatteryOptimizations = powerManager.isIgnoringBatteryOptimizations(context.packageName)
|
||||||
|
|
||||||
|
if (!isIgnoringBatteryOptimizations) {
|
||||||
|
// Toast.makeText(context, "Please allow unrestricted background activity for this app", Toast.LENGTH_LONG).show()
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(R.string.unrestricted_background_permission_text)
|
||||||
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
var intent = Intent()
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
intent.action = Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS
|
||||||
|
// intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).also {
|
||||||
|
// val uri = Uri.parse("package:$packageName")
|
||||||
|
// it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
// it.data = uri
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
context.startActivity(intent)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int -> }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var prevState: Int = 0
|
private var prevState: Int = 0
|
||||||
private val bottomSheetCallback: BottomSheetCallback = @UnstableApi object : BottomSheetCallback() {
|
private val bottomSheetCallback: BottomSheetCallback = @UnstableApi object : BottomSheetCallback() {
|
||||||
override fun onStateChanged(view: View, state: Int) {
|
override fun onStateChanged(view: View, state: Int) {
|
||||||
|
@ -200,10 +260,17 @@ class MainActivity : CastEnabledActivity() {
|
||||||
mainView = findViewById(R.id.main_view)
|
mainView = findViewById(R.id.main_view)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 33 && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT >= 33 && checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
// Toast.makeText(this, R.string.notification_permission_text, Toast.LENGTH_LONG).show()
|
// requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
// requestPostNotificationPermission()
|
MaterialAlertDialogBuilder(this)
|
||||||
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
.setMessage(R.string.notification_permission_text)
|
||||||
}
|
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||||
|
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
|
||||||
|
}
|
||||||
|
.setNegativeButton(R.string.cancel_label) { _: DialogInterface?, _: Int ->
|
||||||
|
checkAndRequestUnrestrictedBackgroundActivity(this@MainActivity)
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
} else checkAndRequestUnrestrictedBackgroundActivity(this)
|
||||||
|
|
||||||
// Consume navigation bar insets - we apply them in setPlayerVisible()
|
// Consume navigation bar insets - we apply them in setPlayerVisible()
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(mainView) { _: View?, insets: WindowInsetsCompat ->
|
ViewCompat.setOnApplyWindowInsetsListener(mainView) { _: View?, insets: WindowInsetsCompat ->
|
||||||
|
@ -750,6 +817,9 @@ class MainActivity : CastEnabledActivity() {
|
||||||
const val MAIN_FRAGMENT_TAG: String = "main"
|
const val MAIN_FRAGMENT_TAG: String = "main"
|
||||||
const val PREF_NAME: String = "MainActivityPrefs"
|
const val PREF_NAME: String = "MainActivityPrefs"
|
||||||
|
|
||||||
|
const val REQUEST_CODE_FIRST_PERMISSION = 1001
|
||||||
|
const val REQUEST_CODE_SECOND_PERMISSION = 1002
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun getIntentToOpenFeed(context: Context, feedId: Long): Intent {
|
fun getIntentToOpenFeed(context: Context, feedId: Long): Intent {
|
||||||
val intent = Intent(context.applicationContext, MainActivity::class.java)
|
val intent = Intent(context.applicationContext, MainActivity::class.java)
|
||||||
|
|
|
@ -11,7 +11,6 @@ import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo
|
||||||
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
|
import ac.mdiq.podcini.storage.database.Episodes.setPlayState
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
|
import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
|
import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync
|
|
||||||
import ac.mdiq.podcini.storage.database.Queues
|
import ac.mdiq.podcini.storage.database.Queues
|
||||||
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
|
import ac.mdiq.podcini.storage.database.Queues.addToQueueSync
|
||||||
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
|
import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet
|
||||||
|
@ -80,7 +79,6 @@ import androidx.compose.ui.unit.sp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.constraintlayout.compose.ConstraintLayout
|
import androidx.constraintlayout.compose.ConstraintLayout
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import coil.compose.AsyncImage
|
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
import io.realm.kotlin.notifications.SingleQueryChange
|
import io.realm.kotlin.notifications.SingleQueryChange
|
||||||
import io.realm.kotlin.notifications.UpdatedObject
|
import io.realm.kotlin.notifications.UpdatedObject
|
||||||
|
@ -175,6 +173,7 @@ class EpisodeVM(var episode: Episode) {
|
||||||
inProgressState = changes.obj.isInProgress
|
inProgressState = changes.obj.isInProgress
|
||||||
Logd("EpisodeVM", "mediaMonitor $positionState $inProgressState ${episode.title}")
|
Logd("EpisodeVM", "mediaMonitor $positionState $inProgressState ${episode.title}")
|
||||||
episode = changes.obj
|
episode = changes.obj
|
||||||
|
// Logd("EpisodeVM", "mediaMonitor downloaded: ${changes.obj.media?.downloaded} ${episode.media?.downloaded}")
|
||||||
}
|
}
|
||||||
} else Logd("EpisodeVM", "mediaMonitor index out bound")
|
} else Logd("EpisodeVM", "mediaMonitor index out bound")
|
||||||
}
|
}
|
||||||
|
@ -191,7 +190,7 @@ fun ChooseRatingDialog(selected: List<Episode>, onDismissRequest: () -> Unit) {
|
||||||
Dialog(onDismissRequest = onDismissRequest) {
|
Dialog(onDismissRequest = onDismissRequest) {
|
||||||
Surface(shape = RoundedCornerShape(16.dp)) {
|
Surface(shape = RoundedCornerShape(16.dp)) {
|
||||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)) {
|
||||||
for (rating in Rating.entries) {
|
for (rating in Rating.entries.reversed()) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp).clickable {
|
||||||
for (item in selected) Episodes.setRating(item, rating.code)
|
for (item in selected) Episodes.setRating(item, rating.code)
|
||||||
onDismissRequest()
|
onDismissRequest()
|
||||||
|
@ -557,8 +556,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
||||||
val velocityTracker = remember { VelocityTracker() }
|
val velocityTracker = remember { VelocityTracker() }
|
||||||
val offsetX = remember { Animatable(0f) }
|
val offsetX = remember { Animatable(0f) }
|
||||||
Box(modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
|
Box(modifier = Modifier.fillMaxWidth().pointerInput(Unit) {
|
||||||
detectHorizontalDragGestures(
|
detectHorizontalDragGestures(onDragStart = { velocityTracker.resetTracking() },
|
||||||
onDragStart = { velocityTracker.resetTracking() },
|
|
||||||
onHorizontalDrag = { change, dragAmount ->
|
onHorizontalDrag = { change, dragAmount ->
|
||||||
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
velocityTracker.addPosition(change.uptimeMillis, change.position)
|
||||||
coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) }
|
coroutineScope.launch { offsetX.snapTo(offsetX.value + dragAmount) }
|
||||||
|
@ -573,10 +571,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
||||||
if (velocity > 0) rightSwipeCB?.invoke(vm.episode)
|
if (velocity > 0) rightSwipeCB?.invoke(vm.episode)
|
||||||
else leftSwipeCB?.invoke(vm.episode)
|
else leftSwipeCB?.invoke(vm.episode)
|
||||||
}
|
}
|
||||||
offsetX.animateTo(
|
offsetX.animateTo(targetValue = 0f, animationSpec = tween(500))
|
||||||
targetValue = 0f, // Back to the initial position
|
|
||||||
animationSpec = tween(500) // Adjust animation duration as needed
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -592,121 +587,129 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: SnapshotStateList<EpisodeVM>,
|
||||||
else selected.remove(vms[index].episode)
|
else selected.remove(vms[index].episode)
|
||||||
}
|
}
|
||||||
val textColor = MaterialTheme.colorScheme.onSurface
|
val textColor = MaterialTheme.colorScheme.onSurface
|
||||||
Row (Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
|
Column {
|
||||||
if (false) {
|
val dur = vm.episode.media?.getDuration() ?: 0
|
||||||
val typedValue = TypedValue()
|
val durText = DurationConverter.getDurationStringLong(dur)
|
||||||
LocalContext.current.theme.resolveAttribute(R.attr.dragview_background, typedValue, true)
|
Row(Modifier.background(if (vm.isSelected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface)) {
|
||||||
Icon(painter = painterResource(typedValue.resourceId), tint = textColor,
|
if (false) {
|
||||||
contentDescription = "drag handle",
|
val typedValue = TypedValue()
|
||||||
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically))
|
LocalContext.current.theme.resolveAttribute(R.attr.dragview_background, typedValue, true)
|
||||||
}
|
Icon(painter = painterResource(typedValue.resourceId), tint = textColor, contentDescription = "drag handle",
|
||||||
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
|
modifier = Modifier.width(16.dp).align(Alignment.CenterVertically))
|
||||||
val (imgvCover, checkMark) = createRefs()
|
}
|
||||||
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(vm.episode)
|
ConstraintLayout(modifier = Modifier.width(56.dp).height(56.dp)) {
|
||||||
val painter = rememberAsyncImagePainter(model = imgLoc)
|
val (imgvCover, checkMark) = createRefs()
|
||||||
Image(
|
val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(vm.episode)
|
||||||
painter = painter,
|
val painter = rememberAsyncImagePainter(model = imgLoc)
|
||||||
contentDescription = "imgvCover",
|
Image(painter = painter, contentDescription = "imgvCover",
|
||||||
modifier = Modifier.width(56.dp).height(56.dp)
|
modifier = Modifier.width(56.dp).height(56.dp)
|
||||||
.constrainAs(imgvCover) {
|
.constrainAs(imgvCover) {
|
||||||
top.linkTo(parent.top)
|
top.linkTo(parent.top)
|
||||||
|
bottom.linkTo(parent.bottom)
|
||||||
|
start.linkTo(parent.start)
|
||||||
|
}.clickable(onClick = {
|
||||||
|
Logd(TAG, "icon clicked!")
|
||||||
|
if (selectMode) toggleSelected()
|
||||||
|
else if (vm.episode.feed != null) activity.loadChildFragment(FeedInfoFragment.newInstance(vm.episode.feed!!))
|
||||||
|
})
|
||||||
|
)
|
||||||
|
val alpha = if (vm.playedState) 1.0f else 0f
|
||||||
|
if (vm.playedState) Icon(painter = painterResource(R.drawable.ic_check),
|
||||||
|
tint = textColor,
|
||||||
|
contentDescription = "played_mark",
|
||||||
|
modifier = Modifier.background(Color.Green).alpha(alpha).constrainAs(checkMark) {
|
||||||
bottom.linkTo(parent.bottom)
|
bottom.linkTo(parent.bottom)
|
||||||
start.linkTo(parent.start)
|
end.linkTo(parent.end)
|
||||||
}.clickable(onClick = {
|
|
||||||
Logd(TAG, "icon clicked!")
|
|
||||||
if (selectMode) toggleSelected()
|
|
||||||
else if (vm.episode.feed != null) activity.loadChildFragment(FeedInfoFragment.newInstance(vm.episode.feed!!))
|
|
||||||
})
|
})
|
||||||
)
|
}
|
||||||
val alpha = if (vm.playedState) 1.0f else 0f
|
Column(Modifier.weight(1f).padding(start = 6.dp, end = 6.dp)
|
||||||
if (vm.playedState) Icon(painter = painterResource(R.drawable.ic_check), tint = textColor, contentDescription = "played_mark",
|
.combinedClickable(onClick = {
|
||||||
modifier = Modifier.background(Color.Green).alpha(alpha).constrainAs(checkMark) {
|
Logd(TAG, "clicked: ${vm.episode.title}")
|
||||||
bottom.linkTo(parent.bottom)
|
if (selectMode) toggleSelected()
|
||||||
end.linkTo(parent.end)
|
else activity.loadChildFragment(EpisodeInfoFragment.newInstance(vm.episode))
|
||||||
})
|
}, onLongClick = {
|
||||||
}
|
selectMode = !selectMode
|
||||||
Column(Modifier.weight(1f).padding(start = 6.dp, end = 6.dp)
|
vm.isSelected = selectMode
|
||||||
.combinedClickable(onClick = {
|
selected.clear()
|
||||||
Logd(TAG, "clicked: ${vm.episode.title}")
|
if (selectMode) {
|
||||||
if (selectMode) toggleSelected()
|
selected.add(vms[index].episode)
|
||||||
else activity.loadChildFragment(EpisodeInfoFragment.newInstance(vm.episode))
|
longPressIndex = index
|
||||||
}, onLongClick = {
|
} else {
|
||||||
selectMode = !selectMode
|
selectedSize = 0
|
||||||
vm.isSelected = selectMode
|
longPressIndex = -1
|
||||||
selected.clear()
|
}
|
||||||
if (selectMode) {
|
Logd(TAG, "long clicked: ${vm.episode.title}")
|
||||||
selected.add(vms[index].episode)
|
})) {
|
||||||
longPressIndex = index
|
LaunchedEffect(key1 = queueChanged) {
|
||||||
} else {
|
if (index >= vms.size) return@LaunchedEffect
|
||||||
selectedSize = 0
|
vms[index].inQueueState = curQueue.contains(vms[index].episode)
|
||||||
longPressIndex = -1
|
|
||||||
}
|
}
|
||||||
Logd(TAG, "long clicked: ${vm.episode.title}")
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
})) {
|
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
|
||||||
LaunchedEffect(key1 = queueChanged) {
|
Icon(painter = painterResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo",
|
||||||
if (index>=vms.size) return@LaunchedEffect
|
modifier = Modifier.width(14.dp).height(14.dp))
|
||||||
vms[index].inQueueState = curQueue.contains(vms[index].episode)
|
val ratingIconRes = Rating.fromCode(vm.ratingState).res
|
||||||
}
|
if (vm.ratingState != Rating.UNRATED.code)
|
||||||
val dur = vm.episode.media?.getDuration() ?: 0
|
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||||
val durText = DurationConverter.getDurationStringLong(dur)
|
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(14.dp).height(14.dp))
|
||||||
Row {
|
if (vm.inQueueState)
|
||||||
if (vm.episode.media?.getMediaType() == MediaType.VIDEO)
|
Icon(painter = painterResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist",
|
||||||
Icon(painter = painterResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(14.dp).height(14.dp))
|
modifier = Modifier.width(14.dp).height(14.dp))
|
||||||
val ratingIconRes = Rating.fromCode(vm.ratingState).res
|
val curContext = LocalContext.current
|
||||||
if (vm.ratingState != Rating.UNRATED.code)
|
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " +
|
||||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating", modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(14.dp).height(14.dp))
|
if ((vm.episode.media?.size ?: 0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
|
||||||
if (vm.inQueueState)
|
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
||||||
Icon(painter = painterResource(R.drawable.ic_playlist_play), tint = textColor, contentDescription = "ivInPlaylist", modifier = Modifier.width(14.dp).height(14.dp))
|
|
||||||
val curContext = LocalContext.current
|
|
||||||
val dateSizeText = " · " + formatAbbrev(curContext, vm.episode.getPubDate()) + " · " + durText + " · " + if((vm.episode.media?.size?:0) > 0) Formatter.formatShortFileSize(curContext, vm.episode.media?.size ?: 0) else ""
|
|
||||||
Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium)
|
|
||||||
}
|
|
||||||
Text(vm.episode.title?:"", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
|
||||||
if (InTheatre.isCurMedia(vm.episode.media) || vm.inProgressState) {
|
|
||||||
val pos = vm.positionState
|
|
||||||
vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
|
|
||||||
Logd(TAG, "$index vm.prog: ${vm.prog}")
|
|
||||||
Row {
|
|
||||||
Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall)
|
|
||||||
LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically))
|
|
||||||
Text(durText, color = textColor, style = MaterialTheme.typography.bodySmall)
|
|
||||||
}
|
}
|
||||||
|
Text(vm.episode.title ?: "", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||||
}
|
}
|
||||||
}
|
fun isDownloading(): Boolean {
|
||||||
fun isDownloading(): Boolean {
|
return vms[index].downloadState > DownloadStatus.State.UNKNOWN.ordinal && vms[index].downloadState < DownloadStatus.State.COMPLETED.ordinal
|
||||||
return vms[index].downloadState > DownloadStatus.State.UNKNOWN.ordinal && vms[index].downloadState < DownloadStatus.State.COMPLETED.ordinal
|
|
||||||
}
|
|
||||||
if (actionButton_ == null) {
|
|
||||||
LaunchedEffect(vms[index].downloadState) {
|
|
||||||
if (index>=vms.size) return@LaunchedEffect
|
|
||||||
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media?.downloadUrl?:"") ?: 0
|
|
||||||
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
|
|
||||||
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
|
||||||
vm.actionRes = vm.actionButton!!.getDrawable()
|
|
||||||
}
|
|
||||||
LaunchedEffect(key1 = status) {
|
|
||||||
if (index>=vms.size) return@LaunchedEffect
|
|
||||||
Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}")
|
|
||||||
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
|
||||||
Logd(TAG, "LaunchedEffect vm.actionButton: ${vm.actionButton?.getLabel()}")
|
|
||||||
vm.actionRes = vm.actionButton!!.getDrawable()
|
|
||||||
}
|
}
|
||||||
|
if (actionButton_ == null) {
|
||||||
|
LaunchedEffect(vms[index].downloadState) {
|
||||||
|
if (index >= vms.size) return@LaunchedEffect
|
||||||
|
if (isDownloading()) vm.dlPercent = dls?.getProgress(vms[index].episode.media?.downloadUrl ?: "") ?: 0
|
||||||
|
Logd(TAG, "LaunchedEffect $index downloadState: ${vms[index].downloadState} ${vm.episode.media?.downloaded} ${vm.dlPercent}")
|
||||||
|
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
||||||
|
vm.actionRes = vm.actionButton!!.getDrawable()
|
||||||
|
}
|
||||||
|
LaunchedEffect(key1 = status) {
|
||||||
|
if (index >= vms.size) return@LaunchedEffect
|
||||||
|
Logd(TAG, "LaunchedEffect $index isPlayingState: ${vms[index].isPlayingState} ${vms[index].episode.title}")
|
||||||
|
vm.actionButton = EpisodeActionButton.forItem(vm.episode)
|
||||||
|
Logd(TAG, "LaunchedEffect vm.actionButton: ${vm.actionButton?.getLabel()}")
|
||||||
|
vm.actionRes = vm.actionButton!!.getDrawable()
|
||||||
|
}
|
||||||
// LaunchedEffect(vm.isPlayingState) {
|
// LaunchedEffect(vm.isPlayingState) {
|
||||||
// Logd(TAG, "LaunchedEffect isPlayingState: $index ${vms[index].isPlayingState} ${vm.isPlayingState}")
|
// Logd(TAG, "LaunchedEffect isPlayingState: $index ${vms[index].isPlayingState} ${vm.isPlayingState}")
|
||||||
// vms[index].actionButton = EpisodeActionButton.forItem(vms[index].episode)
|
// vms[index].actionButton = EpisodeActionButton.forItem(vms[index].episode)
|
||||||
// vms[index].actionRes = vm.actionButton.getDrawable()
|
// vms[index].actionRes = vm.actionButton.getDrawable()
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
Box(modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp).align(Alignment.CenterVertically).pointerInput(Unit) {
|
Box(modifier = Modifier.width(40.dp).height(40.dp).padding(end = 10.dp)
|
||||||
detectTapGestures(onLongPress = { vm.showAltActionsDialog = true }, onTap = {
|
.align(Alignment.CenterVertically).pointerInput(Unit) {
|
||||||
vms[index].actionButton?.onClick(activity)
|
detectTapGestures(onLongPress = { vm.showAltActionsDialog = true }, onTap = {
|
||||||
})
|
vms[index].actionButton?.onClick(activity)
|
||||||
}, contentAlignment = Alignment.Center) {
|
})
|
||||||
|
}, contentAlignment = Alignment.Center) {
|
||||||
// actionRes = actionButton.getDrawable()
|
// actionRes = actionButton.getDrawable()
|
||||||
Icon(painter = painterResource(vm.actionRes), tint = textColor, contentDescription = null, modifier = Modifier.width(28.dp).height(32.dp))
|
Icon(painter = painterResource(vm.actionRes), tint = textColor, contentDescription = null, modifier = Modifier.width(28.dp).height(32.dp))
|
||||||
if (isDownloading() && vm.dlPercent >= 0) CircularProgressIndicator(progress = { 0.01f * vm.dlPercent}, strokeWidth = 4.dp, color = textColor, modifier = Modifier.width(30.dp).height(35.dp))
|
if (isDownloading() && vm.dlPercent >= 0) CircularProgressIndicator(progress = { 0.01f * vm.dlPercent },
|
||||||
|
strokeWidth = 4.dp, color = textColor, modifier = Modifier.width(30.dp).height(35.dp))
|
||||||
|
}
|
||||||
|
if (vm.showAltActionsDialog) vm.actionButton?.AltActionsDialog(activity, vm.showAltActionsDialog,
|
||||||
|
onDismiss = { vm.showAltActionsDialog = false })
|
||||||
|
}
|
||||||
|
if (InTheatre.isCurMedia(vm.episode.media) || vm.inProgressState) {
|
||||||
|
val pos = vm.positionState
|
||||||
|
vm.prog = if (dur > 0 && pos >= 0 && dur >= pos) 1.0f * pos / dur else 0f
|
||||||
|
Logd(TAG, "$index vm.prog: ${vm.prog}")
|
||||||
|
Row {
|
||||||
|
Text(DurationConverter.getDurationStringLong(vm.positionState), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||||
|
LinearProgressIndicator(progress = { vm.prog }, modifier = Modifier.weight(1f).height(4.dp).align(Alignment.CenterVertically))
|
||||||
|
Text(durText, color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (vm.showAltActionsDialog) vm.actionButton?.AltActionsDialog(activity, vm.showAltActionsDialog, onDismiss = { vm.showAltActionsDialog = false })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -780,8 +783,8 @@ fun ConfirmAddYoutubeEpisode(sharedUrls: List<String>, showDialog: Boolean, onDi
|
||||||
try {
|
try {
|
||||||
val info = StreamInfo.getInfo(Vista.getService(0), url)
|
val info = StreamInfo.getInfo(Vista.getService(0), url)
|
||||||
val episode = episodeFromStreamInfo(info)
|
val episode = episodeFromStreamInfo(info)
|
||||||
addToYoutubeSyndicate(episode, !audioOnly)
|
val status = addToYoutubeSyndicate(episode, !audioOnly)
|
||||||
if (log != null) upsert(log) { it.status = 1 }
|
if (log != null) upsert(log) { it.status = status }
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
toastMassege = "Receive share error: ${e.message}"
|
toastMassege = "Receive share error: ${e.message}"
|
||||||
Log.e(TAG, toastMassege)
|
Log.e(TAG, toastMassege)
|
||||||
|
|
|
@ -86,7 +86,7 @@ import java.util.concurrent.Semaphore
|
||||||
private var feedID: Long = 0
|
private var feedID: Long = 0
|
||||||
private var feed by mutableStateOf<Feed?>(null)
|
private var feed by mutableStateOf<Feed?>(null)
|
||||||
|
|
||||||
private val episodes = mutableListOf<Episode>()
|
private val episodes = mutableStateListOf<Episode>()
|
||||||
private val vms = mutableStateListOf<EpisodeVM>()
|
private val vms = mutableStateListOf<EpisodeVM>()
|
||||||
|
|
||||||
private var ieMap: Map<Long, Int> = mapOf()
|
private var ieMap: Map<Long, Int> = mapOf()
|
||||||
|
@ -159,7 +159,7 @@ import java.util.concurrent.Semaphore
|
||||||
loadItemsRunning = false
|
loadItemsRunning = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
binding.lazyColumn.setContent {
|
binding.mainView.setContent {
|
||||||
CustomTheme(requireContext()) {
|
CustomTheme(requireContext()) {
|
||||||
if (showRemoveFeedDialog) RemoveFeedDialog(listOf(feed!!), onDismissRequest = {showRemoveFeedDialog = false}) {
|
if (showRemoveFeedDialog) RemoveFeedDialog(listOf(feed!!), onDismissRequest = {showRemoveFeedDialog = false}) {
|
||||||
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
|
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
|
||||||
|
@ -248,7 +248,7 @@ import java.util.concurrent.Semaphore
|
||||||
bottom.linkTo(parent.bottom)
|
bottom.linkTo(parent.bottom)
|
||||||
start.linkTo(parent.start)
|
start.linkTo(parent.start)
|
||||||
}, verticalAlignment = Alignment.CenterVertically) {
|
}, verticalAlignment = Alignment.CenterVertically) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(0.7f))
|
||||||
Icon(painter = painterResource(R.drawable.ic_filter_white), tint = if (filterButColor == Color.White) textColor else filterButColor, contentDescription = "butFilter",
|
Icon(painter = painterResource(R.drawable.ic_filter_white), tint = if (filterButColor == Color.White) textColor else filterButColor, contentDescription = "butFilter",
|
||||||
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).combinedClickable(onClick = filterClickCB, onLongClick = filterLongClickCB))
|
modifier = Modifier.width(40.dp).height(40.dp).padding(3.dp).combinedClickable(onClick = filterClickCB, onLongClick = filterLongClickCB))
|
||||||
Spacer(modifier = Modifier.width(15.dp))
|
Spacer(modifier = Modifier.width(15.dp))
|
||||||
|
@ -259,8 +259,8 @@ import java.util.concurrent.Semaphore
|
||||||
activity.loadChildFragment(fragment, TransitionEffect.SLIDE)
|
activity.loadChildFragment(fragment, TransitionEffect.SLIDE)
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(0.5f))
|
||||||
Text(feed?.episodes?.size?.toString()?:"", textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.bodyLarge)
|
Text(episodes.size.toString() + " / " + feed?.episodes?.size?.toString(), textAlign = TextAlign.Center, color = Color.White, style = MaterialTheme.typography.bodyLarge)
|
||||||
}
|
}
|
||||||
// Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
// Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner",
|
||||||
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
|
// Modifier.width(12.dp).height(12.dp).constrainAs(image1) {
|
||||||
|
|
|
@ -174,7 +174,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
val ratingIconRes = Rating.fromCode(rating).res
|
val ratingIconRes = Rating.fromCode(rating).res
|
||||||
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
Icon(painter = painterResource(ratingIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "rating",
|
||||||
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = {
|
modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(30.dp).height(30.dp).clickable(onClick = {
|
||||||
showChooseRatingDialog = true
|
showChooseRatingDialog = true
|
||||||
}))
|
}))
|
||||||
Spacer(modifier = Modifier.weight(0.2f))
|
Spacer(modifier = Modifier.weight(0.2f))
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ac.mdiq.podcini.ui.fragment
|
||||||
import ac.mdiq.podcini.R
|
import ac.mdiq.podcini.R
|
||||||
import ac.mdiq.podcini.databinding.LogsFragmentBinding
|
import ac.mdiq.podcini.databinding.LogsFragmentBinding
|
||||||
import ac.mdiq.podcini.net.feed.FeedUpdateManager
|
import ac.mdiq.podcini.net.feed.FeedUpdateManager
|
||||||
|
import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.getFeed
|
import ac.mdiq.podcini.storage.database.Feeds.getFeed
|
||||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||||
|
@ -101,10 +102,10 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
@Composable
|
@Composable
|
||||||
fun SharedLogView() {
|
fun SharedLogView() {
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
val showDialog = remember { mutableStateOf(false) }
|
val showSharedDialog = remember { mutableStateOf(false) }
|
||||||
val dialogParam = remember { mutableStateOf(ShareLog()) }
|
val sharedlogState = remember { mutableStateOf(ShareLog()) }
|
||||||
if (showDialog.value) {
|
if (showSharedDialog.value) {
|
||||||
SharedDetailDialog(status = dialogParam.value, showDialog = showDialog.value, onDismissRequest = { showDialog.value = false })
|
SharedDetailDialog(status = sharedlogState.value, showDialog = showSharedDialog.value, onDismissRequest = { showSharedDialog.value = false })
|
||||||
}
|
}
|
||||||
var showYTMediaConfirmDialog by remember { mutableStateOf(false) }
|
var showYTMediaConfirmDialog by remember { mutableStateOf(false) }
|
||||||
var sharedUrl by remember { mutableStateOf("") }
|
var sharedUrl by remember { mutableStateOf("") }
|
||||||
|
@ -116,24 +117,29 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
itemsIndexed(shareLogs) { position, log ->
|
itemsIndexed(shareLogs) { position, log ->
|
||||||
val textColor = MaterialTheme.colorScheme.onSurface
|
val textColor = MaterialTheme.colorScheme.onSurface
|
||||||
Row (modifier = Modifier.clickable {
|
Row (modifier = Modifier.clickable {
|
||||||
if (log.status == 1) {
|
if (log.status < ShareLog.Status.SUCCESS.ordinal) {
|
||||||
showDialog.value = true
|
|
||||||
dialogParam.value = log
|
|
||||||
} else {
|
|
||||||
receiveShared(log.url!!, activity as AppCompatActivity, false) {
|
receiveShared(log.url!!, activity as AppCompatActivity, false) {
|
||||||
sharedUrl = log.url!!
|
sharedUrl = log.url!!
|
||||||
showYTMediaConfirmDialog = true
|
showYTMediaConfirmDialog = true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Logd(TAG, "shared log url: ${log.url}")
|
||||||
|
// val episode = getEpisodeByGuidOrUrl(null, log.url!!, false)
|
||||||
|
// if (episode != null) (activity as MainActivity).loadChildFragment(EpisodeInfoFragment.newInstance(episode))
|
||||||
|
// else {
|
||||||
|
showSharedDialog.value = true
|
||||||
|
sharedlogState.value = log
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
Column {
|
Column {
|
||||||
Row {
|
Row {
|
||||||
val icon = remember { if (log.status == 1) Icons.Filled.Info else Icons.Filled.Warning }
|
val icon = remember { if (log.status == ShareLog.Status.SUCCESS.ordinal) Icons.Filled.Info else Icons.Filled.Warning }
|
||||||
val iconColor = remember { if (log.status == 1) Color.Green else Color.Yellow }
|
val iconColor = remember { if (log.status == ShareLog.Status.SUCCESS.ordinal) Color.Green else Color.Yellow }
|
||||||
Icon(icon, "Info", tint = iconColor, modifier = Modifier.padding(end = 2.dp))
|
Icon(icon, "Info", tint = iconColor, modifier = Modifier.padding(end = 2.dp))
|
||||||
Text(formatDateTimeFlex(Date(log.id)), color = textColor)
|
Text(formatDateTimeFlex(Date(log.id)), color = textColor)
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
var showAction by remember { mutableStateOf(log.status != 1) }
|
var showAction by remember { mutableStateOf(log.status < ShareLog.Status.SUCCESS.ordinal) }
|
||||||
if (true || showAction) {
|
if (true || showAction) {
|
||||||
Icon(painter = painterResource(R.drawable.ic_delete), tint = textColor, contentDescription = null,
|
Icon(painter = painterResource(R.drawable.ic_delete), tint = textColor, contentDescription = null,
|
||||||
modifier = Modifier.width(25.dp).height(25.dp).clickable {
|
modifier = Modifier.width(25.dp).height(25.dp).clickable {
|
||||||
|
@ -141,9 +147,18 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Text(log.url?:"unknown", color = textColor)
|
Text(log.url?:"unknown", color = textColor)
|
||||||
val statusText = remember {"" }
|
val statusText = when (log.status) {
|
||||||
Text(statusText, color = textColor)
|
ShareLog.Status.ERROR.ordinal -> ShareLog.Status.ERROR.name
|
||||||
if (log.status != 1) {
|
ShareLog.Status.SUCCESS.ordinal -> ShareLog.Status.SUCCESS.name
|
||||||
|
ShareLog.Status.EXISTING.ordinal -> ShareLog.Status.EXISTING.name
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
Text(statusText, color = textColor)
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
Text(log.type?:"unknow type", color = textColor)
|
||||||
|
}
|
||||||
|
if (log.status < ShareLog.Status.SUCCESS.ordinal) {
|
||||||
Text(log.details, color = Color.Red)
|
Text(log.details, color = Color.Red)
|
||||||
Text(stringResource(R.string.download_error_tap_for_details), color = textColor)
|
Text(stringResource(R.string.download_error_tap_for_details), color = textColor)
|
||||||
}
|
}
|
||||||
|
@ -355,9 +370,12 @@ class LogsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
@Composable
|
@Composable
|
||||||
fun SharedDetailDialog(status: ShareLog, showDialog: Boolean, onDismissRequest: () -> Unit) {
|
fun SharedDetailDialog(status: ShareLog, showDialog: Boolean, onDismissRequest: () -> Unit) {
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
var message = requireContext().getString(R.string.download_successful)
|
val message = when (status.status) {
|
||||||
if (status.status == 0) message = status.details
|
ShareLog.Status.ERROR.ordinal -> status.details
|
||||||
|
ShareLog.Status.SUCCESS.ordinal -> stringResource(R.string.download_successful)
|
||||||
|
ShareLog.Status.EXISTING.ordinal -> stringResource(R.string.share_existing)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
Dialog(onDismissRequest = { onDismissRequest() }) {
|
Dialog(onDismissRequest = { onDismissRequest() }) {
|
||||||
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(10.dp), shape = RoundedCornerShape(16.dp)) {
|
Card(modifier = Modifier.wrapContentSize(align = Alignment.Center).padding(10.dp), shape = RoundedCornerShape(16.dp)) {
|
||||||
Column(modifier = Modifier.padding(10.dp)) {
|
Column(modifier = Modifier.padding(10.dp)) {
|
||||||
|
|
|
@ -33,7 +33,6 @@ import ac.mdiq.podcini.util.Logd
|
||||||
import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev
|
import ac.mdiq.podcini.util.MiscFormatter.formatAbbrev
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.Activity.RESULT_OK
|
import android.app.Activity.RESULT_OK
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
|
@ -86,8 +85,6 @@ import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import com.google.android.material.appbar.MaterialToolbar
|
import com.google.android.material.appbar.MaterialToolbar
|
||||||
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.bottomsheet.BottomSheetDialogFragment
|
||||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
@ -125,7 +122,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
private var feedCount by mutableStateOf("")
|
private var feedCount by mutableStateOf("")
|
||||||
private var feedSorted by mutableIntStateOf(0)
|
private var feedSorted by mutableIntStateOf(0)
|
||||||
|
|
||||||
private var feedList: MutableList<Feed> = mutableListOf()
|
// private var feedList: MutableList<Feed> = mutableListOf()
|
||||||
private var feedListFiltered = mutableStateListOf<Feed>()
|
private var feedListFiltered = mutableStateListOf<Feed>()
|
||||||
|
|
||||||
private var useGrid by mutableStateOf<Boolean?>(null)
|
private var useGrid by mutableStateOf<Boolean?>(null)
|
||||||
|
@ -200,7 +197,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
feedCount = feedListFiltered.size.toString() + " / " + feedList.size.toString()
|
feedCount = feedListFiltered.size.toString() + " / " + NavDrawerFragment.feedCount.toString()
|
||||||
loadSubscriptions()
|
loadSubscriptions()
|
||||||
return binding.root
|
return binding.root
|
||||||
}
|
}
|
||||||
|
@ -224,7 +221,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
Logd(TAG, "onDestroyView")
|
Logd(TAG, "onDestroyView")
|
||||||
feedList = mutableListOf()
|
// feedList = mutableListOf()
|
||||||
feedListFiltered.clear()
|
feedListFiltered.clear()
|
||||||
_binding = null
|
_binding = null
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
@ -345,10 +342,11 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
if (!loadItemsRunning) {
|
if (!loadItemsRunning) {
|
||||||
loadItemsRunning = true
|
loadItemsRunning = true
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
|
val feedList: List<Feed>
|
||||||
try {
|
try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
resetTags()
|
resetTags()
|
||||||
filterAndSort()
|
feedList = filterAndSort()
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
// We have fewer items. This can result in items being selected that are no longer visible.
|
// We have fewer items. This can result in items being selected that are no longer visible.
|
||||||
|
@ -356,7 +354,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
// filterOnTag()
|
// filterOnTag()
|
||||||
feedListFiltered.clear()
|
feedListFiltered.clear()
|
||||||
feedListFiltered.addAll(feedList)
|
feedListFiltered.addAll(feedList)
|
||||||
feedCount = feedListFiltered.size.toString() + " / " + feedList.size.toString()
|
feedCount = feedListFiltered.size.toString() + " / " + NavDrawerFragment.feedCount.toString()
|
||||||
infoTextFiltered = " "
|
infoTextFiltered = " "
|
||||||
if (feedsFilter.isNotEmpty()) infoTextFiltered = getString(R.string.filtered_label)
|
if (feedsFilter.isNotEmpty()) infoTextFiltered = getString(R.string.filtered_label)
|
||||||
txtvInformation = (infoTextFiltered + infoTextUpdate)
|
txtvInformation = (infoTextFiltered + infoTextUpdate)
|
||||||
|
@ -368,7 +366,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun filterAndSort() {
|
private fun filterAndSort(): List<Feed> {
|
||||||
var fQueryStr = FeedFilter(feedsFilter).queryString()
|
var fQueryStr = FeedFilter(feedsFilter).queryString()
|
||||||
val tagsQueryStr = queryStringOfTags()
|
val tagsQueryStr = queryStringOfTags()
|
||||||
if (tagsQueryStr.isNotEmpty()) fQueryStr += " AND $tagsQueryStr"
|
if (tagsQueryStr.isNotEmpty()) fQueryStr += " AND $tagsQueryStr"
|
||||||
|
@ -478,8 +476,9 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
comparator(counterMap, dir)
|
comparator(counterMap, dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized(feedList_) { feedList = feedList_.sortedWith(comparator).toMutableList() }
|
// synchronized(feedList_) { feedList = feedList_.sortedWith(comparator).toMutableList() }
|
||||||
feedSorted++
|
feedSorted++
|
||||||
|
return feedList_.sortedWith(comparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun comparator(counterMap: Map<Long, Long>, dir: Int): Comparator<Feed> {
|
private fun comparator(counterMap: Map<Long, Long>, dir: Int): Comparator<Feed> {
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.compose.ui.platform.ComposeView
|
<androidx.compose.ui.platform.ComposeView
|
||||||
android:id="@+id/lazyColumn"
|
android:id="@+id/mainView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,12 @@
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/show_download_logs"
|
||||||
|
android:title="@string/show_download_logs_label"
|
||||||
|
android:icon="@drawable/ic_download"
|
||||||
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/show_shared_logs"
|
android:id="@+id/show_shared_logs"
|
||||||
android:title="@string/show_shared_logs_label"
|
android:title="@string/show_shared_logs_label"
|
||||||
|
@ -15,12 +21,6 @@
|
||||||
android:icon="@drawable/ic_subscriptions"
|
android:icon="@drawable/ic_subscriptions"
|
||||||
app:showAsAction="always" />
|
app:showAsAction="always" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/show_download_logs"
|
|
||||||
android:title="@string/show_download_logs_label"
|
|
||||||
android:icon="@drawable/ic_download"
|
|
||||||
app:showAsAction="always" />
|
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/clear_logs_item"
|
android:id="@+id/clear_logs_item"
|
||||||
android:title="@string/clear_history_label"
|
android:title="@string/clear_history_label"
|
||||||
|
|
|
@ -67,6 +67,8 @@
|
||||||
<string name="tts_not_available">TTS engine not available</string>
|
<string name="tts_not_available">TTS engine not available</string>
|
||||||
<string name="episode_has_no_content">Episode has not content</string>
|
<string name="episode_has_no_content">Episode has not content</string>
|
||||||
|
|
||||||
|
<string name="unrestricted_background_permission_text">In order to successfully play a list of episodes in the background, Podcini needs Permission for Unrestricted Background Activity. Note on some brands, there are sustomized settings for this, please ensure to set them accordingly.</string>
|
||||||
|
|
||||||
<string name="notification_permission_text">Since Android 13, top level notification is needed for normal refresh and playback. You may disallow notifications of sub-catergories at your wish.</string>
|
<string name="notification_permission_text">Since Android 13, top level notification is needed for normal refresh and playback. You may disallow notifications of sub-catergories at your wish.</string>
|
||||||
<string name="notification_permission_denied">You denied the permission.</string>
|
<string name="notification_permission_denied">You denied the permission.</string>
|
||||||
<string name="notification_permission_deny_warning">If you disable notifications and something goes wrong, you might be unable to find out why it went wrong.</string>
|
<string name="notification_permission_deny_warning">If you disable notifications and something goes wrong, you might be unable to find out why it went wrong.</string>
|
||||||
|
@ -312,6 +314,8 @@
|
||||||
<string name="no_items_selected">No items selected</string>
|
<string name="no_items_selected">No items selected</string>
|
||||||
<string name="delete_local_feed_warning_body">Warning: you are deleting local episodes. It will delete the media files from your device storage. It cannot be downloaded again through Podcini. Continue?</string>
|
<string name="delete_local_feed_warning_body">Warning: you are deleting local episodes. It will delete the media files from your device storage. It cannot be downloaded again through Podcini. Continue?</string>
|
||||||
|
|
||||||
|
<string name="share_existing">existing</string>
|
||||||
|
|
||||||
<!-- Download messages and labels -->
|
<!-- Download messages and labels -->
|
||||||
<string name="download_successful">successful</string>
|
<string name="download_successful">successful</string>
|
||||||
<string name="download_pending">Download pending</string>
|
<string name="download_pending">Download pending</string>
|
||||||
|
|
12
changelog.md
12
changelog.md
|
@ -1,3 +1,15 @@
|
||||||
|
# 6.11.5
|
||||||
|
|
||||||
|
* back to be built to target Android 15
|
||||||
|
* request for permission for unrestricted background activity
|
||||||
|
* requests for permission are now less aggressive: if cancelled, Podcini does not quit
|
||||||
|
* reversed rating list in popup (favorite on top)
|
||||||
|
* if a shared Youtube media already exists, record shared log as such
|
||||||
|
* in episodes list, made the progress bar taking the full width
|
||||||
|
* in FeedEpisodes header, the count now reflects filter
|
||||||
|
* in Subscriptions, feed count now reflects filter
|
||||||
|
* minor layout adjustments
|
||||||
|
|
||||||
# 6.11.4
|
# 6.11.4
|
||||||
|
|
||||||
* corrected color contrast in SwipeActions dialog
|
* corrected color contrast in SwipeActions dialog
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
Version 6.11.5
|
||||||
|
|
||||||
|
* back to be built to target Android 15
|
||||||
|
* request for permission for unrestricted background activity
|
||||||
|
* requests for permission are now less aggressive: if cancelled, Podcini does not quit
|
||||||
|
* reversed rating list in popup (favorite on top)
|
||||||
|
* if a shared Youtube media already exists, record shared log as such
|
||||||
|
* in episodes list, made the progress bar taking the full width
|
||||||
|
* in FeedEpisodes header, the count now reflects filter
|
||||||
|
* in Subscriptions, feed count now reflects filter
|
||||||
|
* minor layout adjustments
|
Loading…
Reference in New Issue