diff --git a/app/build.gradle b/app/build.gradle index 0306c5ef..da785c11 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -20,7 +20,7 @@ android { defaultConfig { minSdk 24 compileSdk 35 - targetSdk 35 + targetSdk 30 kotlinOptions { jvmTarget = '17' @@ -31,8 +31,8 @@ android { testApplicationId "ac.mdiq.podcini.tests" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 3020261 - versionName "6.8.4" + versionCode 3020262 + versionName "6.8.5" applicationId "ac.mdiq.podcini.R" def commit = "" diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 50c4c9aa..e3e5fe3f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -73,6 +73,7 @@ import ac.mdiq.vista.extractor.stream.AudioStream import ac.mdiq.vista.extractor.stream.DeliveryMethod import ac.mdiq.vista.extractor.stream.VideoStream import android.annotation.SuppressLint +import android.app.Notification import android.app.NotificationManager import android.app.PendingIntent import android.app.PendingIntent.FLAG_IMMUTABLE @@ -102,6 +103,7 @@ import android.webkit.URLUtil import android.widget.Toast import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE import androidx.core.util.Consumer import androidx.media3.common.* import androidx.media3.common.Player.* @@ -891,12 +893,12 @@ class PlaybackService : MediaLibraryService() { else PendingIntent.getService(this, R.id.pending_intent_allow_stream_always, intentAlwaysAllow, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE) - val builder = NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID.user_action.name) + val builder = Notification.Builder(this, NotificationUtils.CHANNEL_ID.user_action.name) .setSmallIcon(R.drawable.ic_notification_stream) .setContentTitle(getString(R.string.confirm_mobile_streaming_notification_title)) .setContentText(getString(R.string.confirm_mobile_streaming_notification_message)) - .setStyle(NotificationCompat.BigTextStyle().bigText(getString(R.string.confirm_mobile_streaming_notification_message))) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setStyle(Notification.BigTextStyle().bigText(getString(R.string.confirm_mobile_streaming_notification_message))) +// .setPriority(Notification.PRIORITY_DEFAULT) .setContentIntent(pendingIntentAllowThisTime) .addAction(R.drawable.ic_notification_stream, getString(R.string.confirm_mobile_streaming_button_once), pendingIntentAllowThisTime) .addAction(R.drawable.ic_notification_stream, getString(R.string.confirm_mobile_streaming_button_always), pendingIntentAlwaysAllow) @@ -1633,12 +1635,12 @@ class PlaybackService : MediaLibraryService() { } override fun resume() { + Logd(TAG, "resume(): exoPlayer?.playbackState: ${exoPlayer?.playbackState}") if (status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED) { Logd(TAG, "Resuming/Starting playback") acquireWifiLockIfNecessary() setPlaybackParams(getCurrentPlaybackSpeed(curMedia), isSkipSilence) setVolume(1.0f, 1.0f) - if (curMedia != null && status == PlayerStatus.PREPARED && curMedia!!.getPosition() > 0) { val newPosition = calculatePositionWithRewind(curMedia!!.getPosition(), curMedia!!.getLastPlayedTime()) seekTo(newPosition) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt index b1a16104..345b52d6 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Episodes.kt @@ -210,7 +210,7 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList + itemsIndexed(episodes, key = {index, episode -> episode.id}) { index, episode -> var positionState by remember { mutableStateOf(episode.media?.position?:0) } var playedState by remember { mutableStateOf(episode.isPlayed()) } var farvoriteState by remember { mutableStateOf(episode.isFavorite) } @@ -227,7 +227,7 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList { Logd(TAG, "episodeMonitor UpdatedObject $index ${changes.obj.title} ${changes.changedFields.joinToString()}") - if (episodes[index].id == changes.obj.id) { + if (index < episodes.size && episodes[index].id == changes.obj.id) { playedState = changes.obj.isPlayed() farvoriteState = changes.obj.isFavorite // episodes[index] = changes.obj // direct assignment doesn't update member like media?? @@ -250,7 +250,7 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList { Logd(TAG, "mediaMonitor UpdatedObject $index ${changes.obj.title} ${changes.changedFields.joinToString()}") - if (episodes[index].id == changes.obj.id) { + if (index < episodes.size && episodes[index].id == changes.obj.id) { positionState = changes.obj.media?.position ?: 0 inProgressState = changes.obj.isInProgress // episodes[index] = changes.obj // direct assignment doesn't update member like media?? @@ -365,8 +365,8 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList=episodes.size) return@LaunchedEffect inQueueState = curQueue.contains(episodes[index]) } - val dur = remember(episode, episode.media) { episode.media!!.getDuration()} - val durText = remember { DurationConverter.getDurationStringLong(dur) } + val dur = episode.media!!.getDuration() + val durText = DurationConverter.getDurationStringLong(dur) Row { if (episode.media?.getMediaType() == MediaType.VIDEO) Icon(painter = painterResource(R.drawable.ic_videocam), tint = textColor, contentDescription = "isVideo", modifier = Modifier.width(14.dp).height(14.dp)) @@ -375,7 +375,7 @@ fun EpisodeLazyColumn(activity: MainActivity, episodes: SnapshotStateList 0) Formatter.formatShortFileSize(curContext, episode.media!!.size) else "" } + val dateSizeText = " · " + formatAbbrev(curContext, episode.getPubDate()) + " · " + durText + " · " + if((episode.media?.size?:0) > 0) Formatter.formatShortFileSize(curContext, episode.media!!.size) else "" Text(dateSizeText, color = textColor, style = MaterialTheme.typography.bodyMedium) } Text(episode.title?:"", color = textColor, maxLines = 2, overflow = TextOverflow.Ellipsis) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt index b35a979a..1308235c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt @@ -146,15 +146,15 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { start.linkTo(parent.start) }, verticalAlignment = Alignment.CenterVertically) { Spacer(modifier = Modifier.weight(1f)) - Button(onClick = { (activity as MainActivity).loadChildFragment(FeedEpisodesFragment.newInstance(feed.id)) }) { - Text(feed.episodes.size.toString() + stringResource(R.string.episodes_label), color = textColor) - } - Spacer(modifier = Modifier.width(15.dp)) Image(painter = painterResource(R.drawable.ic_settings_white), contentDescription = "butShowSettings", Modifier.width(40.dp).height(40.dp).padding(3.dp).clickable(onClick = { (activity as MainActivity).loadChildFragment(FeedSettingsFragment.newInstance(feed), TransitionEffect.SLIDE) })) - Spacer(modifier = Modifier.weight(1f)) + Spacer(modifier = Modifier.weight(0.2f)) + Button(onClick = { (activity as MainActivity).loadChildFragment(FeedEpisodesFragment.newInstance(feed.id)) }) { + Text(feed.episodes.size.toString() + stringResource(R.string.episodes_label), color = textColor) + } + Spacer(modifier = Modifier.width(15.dp)) } Image(painter = painterResource(R.drawable.ic_rounded_corner_left), contentDescription = "left_corner", Modifier.width(12.dp).height(12.dp).constrainAs(image1) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index 49d64b8f..6f4fbe6a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -98,6 +98,8 @@ import kotlin.math.max private lateinit var toolbar: MaterialToolbar private lateinit var swipeActions: SwipeActions + private var infoTextUpdate = "" + private var infoText = "" private var infoBarText = mutableStateOf("") private var leftActionState = mutableStateOf(null) private var rightActionState = mutableStateOf(null) @@ -267,7 +269,10 @@ import kotlin.math.max Logd(TAG, "Received sticky event: ${event.TAG}") when (event) { is FlowEvent.EpisodeDownloadEvent -> onEpisodeDownloadEvent(event) -// is FlowEvent.FeedUpdatingEvent -> swipeRefreshLayout.isRefreshing = event.isRunning + is FlowEvent.FeedUpdatingEvent -> { + infoTextUpdate = if (event.isRunning) "U" else "" + infoBarText.value = "$infoText $infoTextUpdate" + } else -> {} } } @@ -604,7 +609,7 @@ import kotlin.math.max } private fun refreshInfoBar() { - var info = String.format(Locale.getDefault(), "%d%s", queueItems.size, getString(R.string.episodes_suffix)) + infoText = String.format(Locale.getDefault(), "%d%s", queueItems.size, getString(R.string.episodes_suffix)) if (queueItems.isNotEmpty()) { var timeLeft: Long = 0 for (item in queueItems) { @@ -616,11 +621,10 @@ import kotlin.math.max timeLeft = (timeLeft + itemTimeLeft / playbackSpeed).toLong() } } - info += " • " - info += DurationConverter.getDurationStringLocalized(requireActivity(), timeLeft) + infoText += " • " + infoText += DurationConverter.getDurationStringLocalized(requireActivity(), timeLeft) } - infoBarText.value = info -// toolbar.title = "${getString(R.string.queue_label)}: ${curQueue.name}" + infoBarText.value = "$infoText $infoTextUpdate" } private var loadItemsRunning = false diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt index 35e76198..ccf4d71a 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/SubscriptionsFragment.kt @@ -833,7 +833,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { horizontalArrangement = Arrangement.spacedBy(16.dp), contentPadding = PaddingValues(start = 12.dp, top = 16.dp, end = 12.dp, bottom = 16.dp) ) { - items(feedListFiltered.size) { index -> + items(feedListFiltered.size, key = {index -> feedListFiltered[index].id}) { index -> val feed by remember { mutableStateOf(feedListFiltered[index]) } var isSelected by remember { mutableStateOf(false) } LaunchedEffect(key1 = selectMode, key2 = selectedSize) { @@ -895,7 +895,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener { LazyColumn(state = lazyListState, modifier = Modifier.padding(start = 10.dp, end = 10.dp, top = 10.dp, bottom = 10.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { - itemsIndexed(feedListFiltered) { index, feed -> + itemsIndexed(feedListFiltered, key = {index, feed -> feed.id}) { index, feed -> var isSelected by remember { mutableStateOf(false) } LaunchedEffect(key1 = selectMode, key2 = selectedSize) { isSelected = selectMode && feed in selected diff --git a/changelog.md b/changelog.md index aff4a543..ef0db2be 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,21 @@ +# 6.8.5 + +* migrated mostly the following view to Jetpack Compose: + * Queues, AudioPlayer, Subscriptions, FeedInfo, EpisodeInfo, FeedEpisodes, AllEpisodes, History, Search, and OnlineFeed +* to counter this nasty issue that Google can't fix over 2 years: ForegroundServiceStartNotAllowedException + * for this and near future releases, target SDK is set to 30 (Android 12), though built with SDK 35 and tested on Android 14 + * supposedly notification will not disappear and play will not stop through a playlist + * please voice any irregularities you may see +* on episode lists, show duration on the top row +* added toggle grid and list views in the menu of Subscriptions +* added option to refresh all subscriptions in menu of Queues view +* added telltale of subscription updating status "U" on infobar in Queues view +* AudioPlayer got overhauled. with PlayUI and PlayerDetailed fragments Removed +* Episodes viewholder, feed viewholder, and related adapters etc are removed +* SwipeActions class stripped out of View related operations +* migrated reliance on compose.material to compose.material3 +* tuned and corrected some Compose issues + # 6.8.4 (Release candidate) * on episode lists, show duration on the top row diff --git a/fastlane/metadata/android/en-US/changelogs/3020262.txt b/fastlane/metadata/android/en-US/changelogs/3020262.txt new file mode 100644 index 00000000..66610d84 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020262.txt @@ -0,0 +1,17 @@ + Version 6.8.5 + +* migrated mostly the following view to Jetpack Compose: + * Queues, AudioPlayer, Subscriptions, FeedInfo, EpisodeInfo, FeedEpisodes, AllEpisodes, History, Search, and OnlineFeed +* to counter this nasty issue that Google can't fix over 2 years: ForegroundServiceStartNotAllowedException + * for this and near future releases, target SDK is set to 30 (Android 12), though built with SDK 35 and tested on Android 14 + * supposedly notification will not disappear and play will not stop through a playlist + * please voice any irregularities you may see +* on episode lists, show duration on the top row +* added toggle grid and list views in the menu of Subscriptions +* added option to refresh all subscriptions in menu of Queues view +* added telltale of subscription updating status "U" on infobar in Queues view +* AudioPlayer got overhauled. with PlayUI and PlayerDetailed fragments Removed +* Episodes viewholder, feed viewholder, and related adapters etc are removed +* SwipeActions class stripped out of View related operations +* migrated reliance on compose.material to compose.material3 +* tuned and corrected some Compose issues