diff --git a/README.md b/README.md index a85af7d1..2d1c42d3 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,15 @@ Version 4.1 brings a more convenient player control and tags enhancements, while ## Screenshots - + - + - + - + + + ## Changelogs diff --git a/app/build.gradle b/app/build.gradle index e833e22e..91a6be22 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -149,8 +149,8 @@ android { // Version code schema (not used): // "1.2.3-beta4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 3020110 - versionName "4.2.7" + versionCode 3020111 + versionName "4.3.0" def commit = "" try { diff --git a/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearchResult.kt b/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearchResult.kt index 0ea216e7..adcc9997 100644 --- a/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearchResult.kt +++ b/app/src/main/java/ac/mdiq/podcini/net/discovery/PodcastSearchResult.kt @@ -4,28 +4,21 @@ import ac.mdiq.podcini.net.sync.gpoddernet.model.GpodnetPodcast import de.mfietz.fyydlin.SearchHit import org.json.JSONException import org.json.JSONObject +import java.text.SimpleDateFormat +import java.util.* class PodcastSearchResult private constructor( - /** - * The name of the podcast - */ val title: String, - /** - * URL of the podcast image - */ val imageUrl: String?, - /** - * URL of the podcast feed - */ val feedUrl: String?, - /** - * artistName of the podcast feed - */ - val author: String? + val author: String?, + val count: Int?, + val update: String?, + val source: String ) { companion object { fun dummy(): PodcastSearchResult { - return PodcastSearchResult("", "", "", "") + return PodcastSearchResult("", "", "", "", 0, "", "dummy") } /** @@ -39,7 +32,7 @@ class PodcastSearchResult private constructor( val imageUrl: String? = json.optString("artworkUrl100").takeIf { it.isNotEmpty() } val feedUrl: String? = json.optString("feedUrl").takeIf { it.isNotEmpty() } val author: String? = json.optString("artistName").takeIf { it.isNotEmpty() } - return PodcastSearchResult(title, imageUrl, feedUrl, author) + return PodcastSearchResult(title, imageUrl, feedUrl, author, null, null, "Itunes") } /** @@ -71,21 +64,15 @@ class PodcastSearchResult private constructor( } catch (e: Exception) { // Some feeds have empty artist } - return PodcastSearchResult(title, imageUrl, feedUrl, author) + return PodcastSearchResult(title, imageUrl, feedUrl, author, null, null, "Toplist") } fun fromFyyd(searchHit: SearchHit): PodcastSearchResult { - return PodcastSearchResult(searchHit.title, - searchHit.thumbImageURL, - searchHit.xmlUrl, - searchHit.author) + return PodcastSearchResult(searchHit.title, searchHit.thumbImageURL, searchHit.xmlUrl, searchHit.author, null, null, "Fyyd") } fun fromGpodder(searchHit: GpodnetPodcast): PodcastSearchResult { - return PodcastSearchResult(searchHit.title, - searchHit.logoUrl, - searchHit.url, - searchHit.author) + return PodcastSearchResult(searchHit.title, searchHit.logoUrl, searchHit.url, searchHit.author, null, null, "GPodder") } fun fromPodcastIndex(json: JSONObject): PodcastSearchResult { @@ -93,7 +80,15 @@ class PodcastSearchResult private constructor( val imageUrl: String? = json.optString("image").takeIf { it.isNotEmpty() } val feedUrl: String? = json.optString("url").takeIf { it.isNotEmpty() } val author: String? = json.optString("author").takeIf { it.isNotEmpty() } - return PodcastSearchResult(title, imageUrl, feedUrl, author) + var count: Int? = json.optInt("episodeCount", -1) + if (count != null && count < 0) count = null + val updateInt: Int = json.optInt("lastUpdateTime", -1) + var update: String? = null + if (updateInt > 0) { + val format = SimpleDateFormat("yyyy-MM-dd", Locale.US) + update = format.format(updateInt.toLong() * 1000) + } + return PodcastSearchResult(title, imageUrl, feedUrl, author, count, update, "PodcastIndex") } } } diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt index 019bc1fb..44329526 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/PlaybackPreferences.kt @@ -92,8 +92,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen @JvmStatic fun init(context: Context) { instance = PlaybackPreferences() - prefs = PreferenceManager.getDefaultSharedPreferences( - context!!) + prefs = PreferenceManager.getDefaultSharedPreferences(context) prefs?.registerOnSharedPreferenceChangeListener(instance) } @@ -143,10 +142,9 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, playable.getPlayableType().toLong()) editor.putBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, playable.getMediaType() == MediaType.VIDEO) if (playable is FeedMedia) { - val feedMedia = playable - val itemId = feedMedia.getItem()?.feed?.id + val itemId = playable.getItem()?.feed?.id if (itemId != null) editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, itemId) - editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, feedMedia.id) + editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, playable.id) } else { editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING) editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING) diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt index fef36da6..1f5fe9b5 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -1,5 +1,11 @@ package ac.mdiq.podcini.preferences +import ac.mdiq.podcini.storage.model.download.ProxyConfig +import ac.mdiq.podcini.storage.model.feed.FeedCounter +import ac.mdiq.podcini.storage.model.feed.FeedPreferences.NewEpisodesAction +import ac.mdiq.podcini.storage.model.feed.SortOrder +import ac.mdiq.podcini.storage.model.feed.SubscriptionsFilter +import ac.mdiq.podcini.storage.model.playback.MediaType import android.content.Context import android.content.SharedPreferences import android.os.Build @@ -9,12 +15,6 @@ import android.view.KeyEvent import androidx.annotation.VisibleForTesting import androidx.core.app.NotificationCompat import androidx.preference.PreferenceManager -import ac.mdiq.podcini.storage.model.download.ProxyConfig -import ac.mdiq.podcini.storage.model.feed.FeedCounter -import ac.mdiq.podcini.storage.model.feed.FeedPreferences.NewEpisodesAction -import ac.mdiq.podcini.storage.model.feed.SortOrder -import ac.mdiq.podcini.storage.model.feed.SubscriptionsFilter -import ac.mdiq.podcini.storage.model.playback.MediaType import org.json.JSONArray import org.json.JSONException import java.io.File @@ -847,7 +847,7 @@ object UserPreferences { // prefs.edit().putString(PREF_INBOX_SORTED_ORDER, "" + sortOrder!!.code).apply() // } - @JvmStatic +// @JvmStatic var subscriptionsFilter: SubscriptionsFilter get() { val value = prefs.getString(PREF_FILTER_FEED, "") diff --git a/app/src/main/java/ac/mdiq/podcini/service/playback/ExoPlayerWrapper.kt b/app/src/main/java/ac/mdiq/podcini/service/playback/ExoPlayerWrapper.kt index f8af723c..89649a83 100644 --- a/app/src/main/java/ac/mdiq/podcini/service/playback/ExoPlayerWrapper.kt +++ b/app/src/main/java/ac/mdiq/podcini/service/playback/ExoPlayerWrapper.kt @@ -155,6 +155,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) { @Throws(IllegalStateException::class) fun prepare() { + if (mediaSource == null) return exoPlayer.setMediaSource(mediaSource!!, false) exoPlayer.prepare() } @@ -320,18 +321,12 @@ class ExoPlayerWrapper internal constructor(private val context: Context) { val videoWidth: Int get() { - if (exoPlayer.videoFormat == null) { - return 0 - } - return exoPlayer.videoFormat!!.width + return exoPlayer.videoFormat?.width ?: 0 } val videoHeight: Int get() { - if (exoPlayer.videoFormat == null) { - return 0 - } - return exoPlayer.videoFormat!!.height + return exoPlayer.videoFormat?.height ?: 0 } fun setOnBufferingUpdateListener(bufferingUpdateListener: Consumer?) { diff --git a/app/src/main/java/ac/mdiq/podcini/service/playback/LocalPSMP.kt b/app/src/main/java/ac/mdiq/podcini/service/playback/LocalPSMP.kt index b3f140d3..96cd441a 100644 --- a/app/src/main/java/ac/mdiq/podcini/service/playback/LocalPSMP.kt +++ b/app/src/main/java/ac/mdiq/podcini/service/playback/LocalPSMP.kt @@ -162,22 +162,27 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia callback.onMediaChanged(false) // TODO: speed setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence) - if (stream) { - if (media!!.getStreamUrl() != null) { - if (playable is FeedMedia && playable.getItem()?.feed?.preferences != null) { - val preferences = playable.getItem()!!.feed!!.preferences!! - mediaPlayer?.setDataSource( - media!!.getStreamUrl()!!, - preferences.username, - preferences.password) - } else { - mediaPlayer?.setDataSource(media!!.getStreamUrl()!!) + when { + stream -> { + val streamurl = media!!.getStreamUrl() + if (streamurl != null) { + if (playable is FeedMedia && playable.getItem()?.feed?.preferences != null) { + val preferences = playable.getItem()!!.feed!!.preferences!! + mediaPlayer?.setDataSource( + streamurl, + preferences.username, + preferences.password) + } else { + mediaPlayer?.setDataSource(streamurl) + } } } - } else if (media!!.getLocalMediaUrl() != null && File(media!!.getLocalMediaUrl()!!).canRead()) { - mediaPlayer?.setDataSource(media!!.getLocalMediaUrl()!!) - } else { - throw IOException("Unable to read local file " + media!!.getLocalMediaUrl()) + else -> { + val localMediaurl = media!!.getLocalMediaUrl() + if (localMediaurl != null && File(localMediaurl).canRead()) { + mediaPlayer?.setDataSource(localMediaurl) + } else throw IOException("Unable to read local file $localMediaurl") + } } val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager if (uiModeManager.currentModeType != Configuration.UI_MODE_TYPE_CAR) { diff --git a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt index 561fa25a..ec0f2bb0 100644 --- a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt +++ b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt @@ -1,39 +1,19 @@ package ac.mdiq.podcini.service.playback import ac.mdiq.podcini.R -import android.Manifest -import android.annotation.SuppressLint -import android.app.NotificationManager -import android.app.PendingIntent -import android.app.UiModeManager -import android.bluetooth.BluetoothA2dp -import android.content.* -import android.content.pm.PackageManager -import android.content.res.Configuration -import android.media.AudioManager -import android.net.Uri -import android.os.* -import android.service.quicksettings.TileService -import android.support.v4.media.MediaBrowserCompat -import android.support.v4.media.MediaDescriptionCompat -import android.support.v4.media.MediaMetadataCompat -import android.support.v4.media.session.MediaSessionCompat -import android.support.v4.media.session.PlaybackStateCompat -import android.text.TextUtils -import android.util.Log -import android.util.Pair -import android.view.KeyEvent -import android.view.SurfaceHolder -import android.view.ViewConfiguration -import android.webkit.URLUtil -import android.widget.Toast -import androidx.annotation.DrawableRes -import androidx.annotation.StringRes -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import androidx.media.MediaBrowserServiceCompat -import androidx.media3.common.util.UnstableApi +import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink +import ac.mdiq.podcini.playback.PlayableUtils.saveCurrentPosition +import ac.mdiq.podcini.playback.PlaybackServiceStarter +import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer +import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPCallback +import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPInfo +import ac.mdiq.podcini.playback.base.PlayerStatus +import ac.mdiq.podcini.playback.cast.CastPsmp +import ac.mdiq.podcini.playback.cast.CastStateListener +import ac.mdiq.podcini.playback.event.BufferUpdateEvent +import ac.mdiq.podcini.playback.event.PlaybackPositionEvent +import ac.mdiq.podcini.playback.event.PlaybackServiceEvent +import ac.mdiq.podcini.playback.event.SleepTimerUpdatedEvent import ac.mdiq.podcini.preferences.PlaybackPreferences import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.clearCurrentlyPlayingTemporaryPlaybackSpeed import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.createInstanceFromPreferences @@ -49,38 +29,6 @@ import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableFrom import ac.mdiq.podcini.preferences.SleepTimerPreferences.autoEnableTo import ac.mdiq.podcini.preferences.SleepTimerPreferences.isInTimeRange import ac.mdiq.podcini.preferences.SleepTimerPreferences.timerMillis -import ac.mdiq.podcini.receiver.MediaButtonReceiver -import ac.mdiq.podcini.service.QuickSettingsTileService -import ac.mdiq.podcini.service.playback.PlaybackServiceTaskManager.PSTMCallback -import ac.mdiq.podcini.storage.DBReader -import ac.mdiq.podcini.storage.DBWriter -import ac.mdiq.podcini.storage.FeedSearcher -import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink -import ac.mdiq.podcini.util.ChapterUtils.getCurrentChapterIndex -import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded -import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed -import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast -import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed -import ac.mdiq.podcini.ui.gui.NotificationUtils -import ac.mdiq.podcini.playback.PlayableUtils.saveCurrentPosition -import ac.mdiq.podcini.playback.PlaybackServiceStarter -import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState -import ac.mdiq.podcini.playback.event.BufferUpdateEvent -import ac.mdiq.podcini.playback.event.PlaybackPositionEvent -import ac.mdiq.podcini.playback.event.PlaybackServiceEvent -import ac.mdiq.podcini.playback.event.SleepTimerUpdatedEvent -import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent -import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent -import ac.mdiq.podcini.storage.model.feed.* -import ac.mdiq.podcini.storage.model.feed.FeedPreferences.AutoDeleteAction -import ac.mdiq.podcini.storage.model.playback.MediaType -import ac.mdiq.podcini.storage.model.playback.Playable -import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer -import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPCallback -import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer.PSMPInfo -import ac.mdiq.podcini.playback.base.PlayerStatus -import ac.mdiq.podcini.playback.cast.CastPsmp -import ac.mdiq.podcini.playback.cast.CastStateListener import ac.mdiq.podcini.preferences.UserPreferences.allEpisodesSortOrder import ac.mdiq.podcini.preferences.UserPreferences.downloadsSortedOrder import ac.mdiq.podcini.preferences.UserPreferences.fastForwardSecs @@ -103,10 +51,61 @@ import ac.mdiq.podcini.preferences.UserPreferences.showNextChapterOnFullNotifica import ac.mdiq.podcini.preferences.UserPreferences.showPlaybackSpeedOnFullNotification import ac.mdiq.podcini.preferences.UserPreferences.showSkipOnFullNotification import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed -import ac.mdiq.podcini.service.download.NewEpisodesNotification +import ac.mdiq.podcini.receiver.MediaButtonReceiver +import ac.mdiq.podcini.service.QuickSettingsTileService +import ac.mdiq.podcini.service.playback.PlaybackServiceTaskManager.PSTMCallback +import ac.mdiq.podcini.storage.DBReader +import ac.mdiq.podcini.storage.DBWriter +import ac.mdiq.podcini.storage.FeedSearcher +import ac.mdiq.podcini.storage.model.feed.* +import ac.mdiq.podcini.storage.model.feed.FeedPreferences.AutoDeleteAction +import ac.mdiq.podcini.storage.model.playback.MediaType +import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter import ac.mdiq.podcini.ui.appstartintent.VideoPlayerActivityStarter +import ac.mdiq.podcini.ui.gui.NotificationUtils +import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState +import ac.mdiq.podcini.util.ChapterUtils.getCurrentChapterIndex +import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded +import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed +import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast +import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed +import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent +import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent +import android.Manifest +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.app.PendingIntent +import android.app.UiModeManager +import android.bluetooth.BluetoothA2dp +import android.content.* +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.media.AudioManager +import android.net.Uri +import android.os.* import android.os.Build.VERSION_CODES +import android.service.quicksettings.TileService +import android.support.v4.media.MediaBrowserCompat +import android.support.v4.media.MediaDescriptionCompat +import android.support.v4.media.MediaMetadataCompat +import android.support.v4.media.session.MediaSessionCompat +import android.support.v4.media.session.PlaybackStateCompat +import android.text.TextUtils +import android.util.Log +import android.util.Pair +import android.view.KeyEvent +import android.view.SurfaceHolder +import android.view.ViewConfiguration +import android.webkit.URLUtil +import android.widget.Toast +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import androidx.media.MediaBrowserServiceCompat +import androidx.media3.common.util.UnstableApi import io.reactivex.* import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers @@ -389,28 +388,35 @@ class PlaybackService : MediaBrowserServiceCompat() { } val feedItems: List - if (parentId == resources.getString(R.string.queue_label)) { - feedItems = DBReader.getQueue() - } else if (parentId == resources.getString(R.string.downloads_label)) { - feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, - FeedItemFilter(FeedItemFilter.DOWNLOADED), downloadsSortedOrder) - } else if (parentId == resources.getString(R.string.episodes_label)) { - feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, - FeedItemFilter(FeedItemFilter.UNPLAYED), allEpisodesSortOrder) - } else if (parentId.startsWith("FeedId:")) { - val feedId = parentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].toLong() - val feed = DBReader.getFeed(feedId) - feedItems = DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed!!.sortOrder) - } else if (parentId == getString(R.string.current_playing_episode)) { - val playable = createInstanceFromPreferences(this) - if (playable is FeedMedia) { - feedItems = listOf(playable.getItem()) - } else { + when { + parentId == resources.getString(R.string.queue_label) -> { + feedItems = DBReader.getQueue() + } + parentId == resources.getString(R.string.downloads_label) -> { + feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, + FeedItemFilter(FeedItemFilter.DOWNLOADED), downloadsSortedOrder) + } + parentId == resources.getString(R.string.episodes_label) -> { + feedItems = DBReader.getEpisodes(0, MAX_ANDROID_AUTO_EPISODES_PER_FEED, + FeedItemFilter(FeedItemFilter.UNPLAYED), allEpisodesSortOrder) + } + parentId.startsWith("FeedId:") -> { + val feedId = parentId.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1].toLong() + val feed = DBReader.getFeed(feedId) + feedItems = if (feed != null) DBReader.getFeedItemList(feed, FeedItemFilter.unfiltered(), feed.sortOrder) else listOf() + } + parentId == getString(R.string.current_playing_episode) -> { + val playable = createInstanceFromPreferences(this) + if (playable is FeedMedia) { + feedItems = listOf(playable.getItem()) + } else { + return null + } + } + else -> { + Log.e(TAG, "Parent ID not found: $parentId") return null } - } else { - Log.e(TAG, "Parent ID not found: $parentId") - return null } var count = 0 for (feedItem in feedItems) { @@ -510,7 +516,7 @@ class PlaybackService : MediaBrowserServiceCompat() { return } - val preferences = playable.getItem()!!.feed?.preferences + val preferences = playable.getItem()?.feed?.preferences val skipIntro = preferences?.feedSkipIntro ?: 0 val context = applicationContext @@ -725,12 +731,12 @@ class PlaybackService : MediaBrowserServiceCompat() { } private fun startPlaying(playable: Playable?, allowStreamThisTime: Boolean) { - val localFeed = URLUtil.isContentUrl(playable!!.getStreamUrl()) + if (playable == null) return + + val localFeed = URLUtil.isContentUrl(playable.getStreamUrl()) val stream = !playable.localFileAvailable() || localFeed if (stream && !localFeed && !isStreamingAllowed && !allowStreamThisTime) { - displayStreamingNotAllowedNotification( - PlaybackServiceStarter(this, playable) - .intent) + displayStreamingNotAllowedNotification(PlaybackServiceStarter(this, playable).intent) writeNoMediaPlaying() stateManager.stopService() return @@ -784,60 +790,63 @@ class PlaybackService : MediaBrowserServiceCompat() { override fun statusChanged(newInfo: PSMPInfo?) { currentMediaType = mediaPlayer?.getCurrentMediaType() ?: MediaType.UNKNOWN - updateMediaSession(newInfo!!.playerStatus) - when (newInfo.playerStatus) { - PlayerStatus.INITIALIZED -> { - if (mediaPlayer != null) { - writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus) + updateMediaSession(newInfo?.playerStatus) + if (newInfo != null) { + when (newInfo.playerStatus) { + PlayerStatus.INITIALIZED -> { + if (mediaPlayer != null) { + writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus) + } + updateNotificationAndMediaSession(newInfo.playable) } - updateNotificationAndMediaSession(newInfo.playable) - } - PlayerStatus.PREPARED -> { - if (mediaPlayer != null) { - writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus) + PlayerStatus.PREPARED -> { + if (mediaPlayer != null) { + writeMediaPlaying(mediaPlayer!!.pSMPInfo.playable, mediaPlayer!!.pSMPInfo.playerStatus) + } + taskManager.startChapterLoader(newInfo.playable!!) } - taskManager.startChapterLoader(newInfo.playable!!) - } - PlayerStatus.PAUSED -> { - updateNotificationAndMediaSession(newInfo.playable) - if (!isCasting) { - stateManager.stopForeground(!isPersistNotify) - } - cancelPositionObserver() - if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus) - } - PlayerStatus.STOPPED -> {} - PlayerStatus.PLAYING -> { - if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus) - saveCurrentPosition(true, null, Playable.INVALID_TIME) - recreateMediaSessionIfNeeded() - updateNotificationAndMediaSession(newInfo.playable) - setupPositionObserver() - stateManager.validStartCommandWasReceived() - stateManager.startForeground(R.id.notification_playing, notificationBuilder.build()) - // set sleep timer if auto-enabled - var autoEnableByTime = true - val fromSetting = autoEnableFrom() - val toSetting = autoEnableTo() - if (fromSetting != toSetting) { - val now: Calendar = GregorianCalendar() - now.timeInMillis = System.currentTimeMillis() - val currentHour = now[Calendar.HOUR_OF_DAY] - autoEnableByTime = isInTimeRange(fromSetting, toSetting, currentHour) + PlayerStatus.PAUSED -> { + updateNotificationAndMediaSession(newInfo.playable) + if (!isCasting) { + stateManager.stopForeground(!isPersistNotify) + } + cancelPositionObserver() + if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus) } + PlayerStatus.STOPPED -> {} + PlayerStatus.PLAYING -> { + if (mediaPlayer != null) writePlayerStatus(mediaPlayer!!.playerStatus) + saveCurrentPosition(true, null, Playable.INVALID_TIME) + recreateMediaSessionIfNeeded() + updateNotificationAndMediaSession(newInfo.playable) + setupPositionObserver() + stateManager.validStartCommandWasReceived() + stateManager.startForeground(R.id.notification_playing, notificationBuilder.build()) + // set sleep timer if auto-enabled + var autoEnableByTime = true + val fromSetting = autoEnableFrom() + val toSetting = autoEnableTo() + if (fromSetting != toSetting) { + val now: Calendar = GregorianCalendar() + now.timeInMillis = System.currentTimeMillis() + val currentHour = now[Calendar.HOUR_OF_DAY] + autoEnableByTime = isInTimeRange(fromSetting, toSetting, currentHour) + } - if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING && autoEnable() && autoEnableByTime && !sleepTimerActive()) { - setSleepTimer(timerMillis()) - EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(getString(R.string.sleep_timer_enabled_label), - { disableSleepTimer() }, getString(R.string.undo))) + if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING && autoEnable() && autoEnableByTime && !sleepTimerActive()) { + setSleepTimer(timerMillis()) + EventBus.getDefault() + .post(ac.mdiq.podcini.util.event.MessageEvent(getString(R.string.sleep_timer_enabled_label), + { disableSleepTimer() }, getString(R.string.undo))) + } + loadQueueForMediaSession() } - loadQueueForMediaSession() + PlayerStatus.ERROR -> { + writeNoMediaPlaying() + stateManager.stopService() + } + else -> {} } - PlayerStatus.ERROR -> { - writeNoMediaPlaying() - stateManager.stopService() - } - else -> {} } if (Build.VERSION.SDK_INT >= VERSION_CODES.N) { TileService.requestListeningState(applicationContext, @@ -1059,7 +1068,7 @@ class PlaybackService : MediaBrowserServiceCompat() { } var autoSkipped = false - if (autoSkippedFeedMediaId != null && autoSkippedFeedMediaId == item!!.identifyingValue) { + if (autoSkippedFeedMediaId != null && autoSkippedFeedMediaId == item?.identifyingValue) { autoSkippedFeedMediaId = null autoSkipped = true } @@ -1136,7 +1145,7 @@ class PlaybackService : MediaBrowserServiceCompat() { toast.show() this.autoSkippedFeedMediaId = feedMedia.getItem()!!.identifyingValue - mediaPlayer!!.skip() + mediaPlayer?.skip() } } @@ -1201,7 +1210,7 @@ class PlaybackService : MediaBrowserServiceCompat() { } if (showNextChapterOnFullNotification()) { - if (playable != null && playable!!.getChapters().isNotEmpty()) { + if (!playable?.getChapters().isNullOrEmpty()) { sessionState.addCustomAction( PlaybackStateCompat.CustomAction.Builder( CUSTOM_ACTION_NEXT_CHAPTER, @@ -1220,9 +1229,10 @@ class PlaybackService : MediaBrowserServiceCompat() { ) } - WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!) - - mediaSession!!.setPlaybackState(sessionState.build()) + if (mediaSession != null) { + WearMediaSession.mediaSessionSetExtraForWear(mediaSession!!) + mediaSession!!.setPlaybackState(sessionState.build()) + } } private fun updateNotificationAndMediaSession(p: Playable?) { @@ -1252,9 +1262,9 @@ class PlaybackService : MediaBrowserServiceCompat() { val m = p if (m.getItem() != null) { val item = m.getItem() - if (item!!.imageUrl != null) { + if (item?.imageUrl != null) { iconUri = item.imageUrl - } else if (item.feed != null) { + } else if (item?.feed != null) { iconUri = item.feed!!.imageUrl } } @@ -1289,9 +1299,8 @@ class PlaybackService : MediaBrowserServiceCompat() { @Synchronized private fun setupNotification(playable: Playable?) { Log.d(TAG, "setupNotification") - if (playableIconLoaderThread != null) { - playableIconLoaderThread!!.interrupt() - } + playableIconLoaderThread?.interrupt() + if (playable == null || mediaPlayer == null) { Log.d(TAG, "setupNotification: playable=$playable") Log.d(TAG, "setupNotification: mediaPlayer=$mediaPlayer") @@ -1303,7 +1312,7 @@ class PlaybackService : MediaBrowserServiceCompat() { val playerStatus = mediaPlayer!!.playerStatus notificationBuilder.setPlayable(playable) - notificationBuilder.setMediaSessionToken(mediaSession!!.sessionToken) + if (mediaSession != null) notificationBuilder.setMediaSessionToken(mediaSession!!.sessionToken) notificationBuilder.playerStatus = playerStatus notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed) @@ -1332,7 +1341,7 @@ class PlaybackService : MediaBrowserServiceCompat() { updateMediaSessionMetadata(playable) } } - playableIconLoaderThread!!.start() + playableIconLoaderThread?.start() } } @@ -1358,7 +1367,7 @@ class PlaybackService : MediaBrowserServiceCompat() { duration = playable?.getDuration() ?: Playable.INVALID_TIME } if (position != Playable.INVALID_TIME && duration != Playable.INVALID_TIME && playable != null) { - Log.d(TAG, "Saving current position to $position") + Log.d(TAG, "Saving current position to $position $duration") saveCurrentPosition(playable, position, System.currentTimeMillis()) } } @@ -1373,11 +1382,11 @@ class PlaybackService : MediaBrowserServiceCompat() { private fun bluetoothNotifyChange(info: PSMPInfo?, whatChanged: String) { var isPlaying = false - if (info!!.playerStatus == PlayerStatus.PLAYING) { + if (info?.playerStatus == PlayerStatus.PLAYING) { isPlaying = true } - if (info.playable != null) { + if (info?.playable != null) { val i = Intent(whatChanged) i.putExtra("id", 1L) i.putExtra("artist", "") @@ -1476,7 +1485,7 @@ class PlaybackService : MediaBrowserServiceCompat() { Log.d(TAG, "pauseIfPauseOnDisconnect()") transientPause = (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) if (isPauseOnHeadsetDisconnect && !isCasting) { - mediaPlayer!!.pause(!isPersistNotify, false) + mediaPlayer?.pause(!isPersistNotify, false) } } @@ -1598,7 +1607,7 @@ class PlaybackService : MediaBrowserServiceCompat() { var isStartWhenPrepared: Boolean get() = mediaPlayer?.isStartWhenPrepared() ?: false set(s) { - if (mediaPlayer != null) mediaPlayer!!.setStartWhenPrepared(s) + mediaPlayer?.setStartWhenPrepared(s) } fun seekTo(t: Int) { @@ -1646,7 +1655,7 @@ class PlaybackService : MediaBrowserServiceCompat() { get() = mediaPlayer?.isStreaming() ?: false val videoSize: Pair? - get() = mediaPlayer!!.getVideoSize() + get() = mediaPlayer?.getVideoSize() private fun setupPositionObserver() { positionEventTimer?.dispose() @@ -1656,8 +1665,7 @@ class PlaybackService : MediaBrowserServiceCompat() { .observeOn(AndroidSchedulers.mainThread()) .subscribe { Log.d(TAG, "notificationBuilder.updatePosition currentPosition: $currentPosition, currentPlaybackSpeed: $currentPlaybackSpeed") - EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, - duration)) + EventBus.getDefault().post(PlaybackPositionEvent(currentPosition, duration)) // TODO: why set SDK_INT < 29 if (Build.VERSION.SDK_INT < 29) { notificationBuilder.updatePosition(currentPosition, currentPlaybackSpeed) @@ -1674,7 +1682,7 @@ class PlaybackService : MediaBrowserServiceCompat() { private fun addPlayableToQueue(playable: Playable?) { if (playable is FeedMedia) { - val itemId = playable.getItem()!!.id + val itemId = playable.getItem()?.id ?: return DBWriter.addQueueItem(this, false, true, itemId) notifyChildrenChanged(getString(R.string.queue_label)) } @@ -1857,9 +1865,6 @@ class PlaybackService : MediaBrowserServiceCompat() { } companion object { - /** - * Logging tag - */ private const val TAG = "PlaybackService" // TODO: need to experiment this value diff --git a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackServiceNotificationBuilder.kt b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackServiceNotificationBuilder.kt index 79c5de95..8362e219 100644 --- a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackServiceNotificationBuilder.kt +++ b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackServiceNotificationBuilder.kt @@ -65,12 +65,16 @@ class PlaybackServiceNotificationBuilder(private val context: Context) { try { cachedIcon = Glide.with(context) .asBitmap() - .load(playable!!.getImageLocation()) + .load(playable?.getImageLocation()) .apply(options) .submit(iconSize, iconSize) .get() } catch (e: ExecutionException) { try { + if (playable == null) { + Log.e(TAG, "playable is null") + return + } cachedIcon = Glide.with(context) .asBitmap() .load(ImageResourceUtils.getFallbackImageLocation(playable!!)) diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt index 3f0895f8..3a00af15 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt @@ -867,7 +867,7 @@ object DBReader { // getFeedList(adapter) // TODO: - if (false || subscriptionsFilter != null) { + if (false && subscriptionsFilter != null) { feeds = subscriptionsFilter.filter(feeds, feedCounters as Map).toMutableList() } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt index c7bd0379..6d0ae10c 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt @@ -58,9 +58,9 @@ class PodDBAdapter private constructor() { * * @return the id of the entry */ - private fun setFeed(feed: Feed?): Long { + private fun setFeed(feed: Feed): Long { val values = ContentValues() - values.put(KEY_TITLE, feed!!.feedTitle) + values.put(KEY_TITLE, feed.feedTitle) values.put(KEY_LINK, feed.link) values.put(KEY_DESCRIPTION, feed.description) values.put(KEY_PAYMENT_LINK, FeedFunding.getPaymentLinksAsString(feed.paymentLinks)) @@ -77,7 +77,7 @@ class PodDBAdapter private constructor() { values.put(KEY_IS_PAGED, feed.isPaged) values.put(KEY_NEXT_PAGE_LINK, feed.nextPageLink) - if (feed.itemFilter != null && feed.itemFilter!!.values.isNotEmpty()) { + if (!feed.itemFilter?.values.isNullOrEmpty()) { values.put(KEY_HIDE, TextUtils.join(",", feed.itemFilter!!.values)) } else { values.put(KEY_HIDE, "") @@ -97,8 +97,9 @@ class PodDBAdapter private constructor() { return feed.id } - fun setFeedPreferences(prefs: FeedPreferences?) { - require(prefs!!.feedID != 0L) { "Feed ID of preference must not be null" } + fun setFeedPreferences(prefs: FeedPreferences) { + require(prefs.feedID != 0L) { "Feed ID of preference must not be null" } + val values = ContentValues() values.put(KEY_AUTO_DOWNLOAD_ENABLED, prefs.autoDownload) values.put(KEY_KEEP_UPDATED, prefs.keepUpdated) @@ -223,7 +224,7 @@ class PodDBAdapter private constructor() { } } if (feed.preferences != null) { - setFeedPreferences(feed.preferences) + setFeedPreferences(feed.preferences!!) } } db.setTransactionSuccessful() @@ -293,10 +294,10 @@ class PodDBAdapter private constructor() { } values.put(KEY_PUBDATE, item.getPubDate()!!.time) values.put(KEY_PAYMENT_LINK, item.paymentLink) - if (saveFeed && item.feed != null) { - setFeed(item.feed) + if (item.feed != null) { + if (saveFeed) setFeed(item.feed!!) + values.put(KEY_FEED, item.feed!!.id) } - values.put(KEY_FEED, item.feed!!.id) if (item.isNew) { values.put(KEY_READ, FeedItem.NEW) } else if (item.isPlayed()) { @@ -1091,7 +1092,7 @@ class PodDBAdapter private constructor() { Log.e(TAG, "Database corrupted: " + db.path) val dbPath = File(db.path) - val backupFolder = context!!.getExternalFilesDir(null) + val backupFolder = context.getExternalFilesDir(null) val backupFile = File(backupFolder, "CorruptedDatabaseBackup.db") try { FileUtils.copyFile(dbPath, backupFile) diff --git a/app/src/main/java/ac/mdiq/podcini/storage/model/playback/RemoteMedia.kt b/app/src/main/java/ac/mdiq/podcini/storage/model/playback/RemoteMedia.kt index 8d3a0d99..64dca8d5 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/model/playback/RemoteMedia.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/model/playback/RemoteMedia.kt @@ -74,6 +74,8 @@ class RemoteMedia : Playable { this.mimeType = item.media!!.mime_type this.pubDate = item.pubDate this.description = item.description + + this.duration = item.media?.getDuration() ?: 0 } fun getEpisodeIdentifier(): String? { @@ -159,7 +161,7 @@ class RemoteMedia : Playable { } override fun getStreamUrl(): String? { - return downloadUrl + return streamUrl } override fun getLocalMediaUrl(): String? { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt index 6f8dba45..bb2af131 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt @@ -1,6 +1,5 @@ package ac.mdiq.podcini.ui.activity -//import ac.mdiq.podcini.ui.home.HomeFragment import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.MainActivityBinding import ac.mdiq.podcini.net.download.FeedUpdateManager @@ -43,7 +42,6 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup.MarginLayoutParams import android.widget.EditText -import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.ActionBarDrawerToggle import androidx.core.graphics.Insets diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/OnlineFeedViewActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/OnlineFeedViewActivity.kt index e624f074..eea23b5b 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/OnlineFeedViewActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/OnlineFeedViewActivity.kt @@ -26,7 +26,7 @@ import com.bumptech.glide.request.RequestOptions import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import ac.mdiq.podcini.R -import ac.mdiq.podcini.ui.adapter.FeedItemlistDescriptionAdapter +import ac.mdiq.podcini.ui.adapter.OnlineItemDescriptionAdapter import ac.mdiq.podcini.feed.FeedUrlNotFoundException import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.currentlyPlayingMediaType import ac.mdiq.podcini.preferences.PlaybackPreferences.Companion.writeNoMediaPlaying @@ -103,29 +103,33 @@ class OnlineFeedViewActivity : AppCompatActivity() { private var parser: Disposable? = null private var updater: Disposable? = null - private lateinit var headerBinding: OnlinefeedviewHeaderBinding - private lateinit var viewBinding: OnlinefeedviewActivityBinding + private lateinit var hBinding: OnlinefeedviewHeaderBinding + private lateinit var binding: OnlinefeedviewActivityBinding override fun onCreate(savedInstanceState: Bundle?) { setTheme(getTranslucentTheme(this)) super.onCreate(savedInstanceState) - viewBinding = OnlinefeedviewActivityBinding.inflate(layoutInflater) - setContentView(viewBinding.root) + binding = OnlinefeedviewActivityBinding.inflate(layoutInflater) + setContentView(binding.root) - viewBinding.transparentBackground.setOnClickListener { finish() } - viewBinding.closeButton.setOnClickListener { finish() } - viewBinding.card.setOnClickListener(null) - viewBinding.card.setCardBackgroundColor(getColorFromAttr(this, R.attr.colorSurface)) - headerBinding = OnlinefeedviewHeaderBinding.inflate(layoutInflater) + binding.transparentBackground.setOnClickListener { finish() } + binding.closeButton.setOnClickListener { finish() } + binding.card.setOnClickListener(null) + binding.card.setCardBackgroundColor(getColorFromAttr(this, R.attr.colorSurface)) + hBinding = OnlinefeedviewHeaderBinding.inflate(layoutInflater) var feedUrl: String? = null - if (intent.hasExtra(ARG_FEEDURL)) { - feedUrl = intent.getStringExtra(ARG_FEEDURL) - } else if (TextUtils.equals(intent.action, Intent.ACTION_SEND)) { - feedUrl = intent.getStringExtra(Intent.EXTRA_TEXT) - } else if (TextUtils.equals(intent.action, Intent.ACTION_VIEW)) { - feedUrl = intent.dataString + when { + intent.hasExtra(ARG_FEEDURL) -> { + feedUrl = intent.getStringExtra(ARG_FEEDURL) + } + TextUtils.equals(intent.action, Intent.ACTION_SEND) -> { + feedUrl = intent.getStringExtra(Intent.EXTRA_TEXT) + } + TextUtils.equals(intent.action, Intent.ACTION_VIEW) -> { + feedUrl = intent.dataString + } } if (feedUrl == null) { @@ -164,8 +168,8 @@ class OnlineFeedViewActivity : AppCompatActivity() { * Displays a progress indicator. */ private fun setLoadingLayout() { - viewBinding.progressBar.visibility = View.VISIBLE - viewBinding.feedDisplayContainer.visibility = View.GONE + binding.progressBar.visibility = View.VISIBLE + binding.feedDisplayContainer.visibility = View.GONE } override fun onStart() { @@ -388,18 +392,18 @@ class OnlineFeedViewActivity : AppCompatActivity() { * This method is executed on the GUI thread. */ @UnstableApi private fun showFeedInformation(feed: Feed, alternateFeedUrls: Map) { - viewBinding.progressBar.visibility = View.GONE - viewBinding.feedDisplayContainer.visibility = View.VISIBLE + binding.progressBar.visibility = View.GONE + binding.feedDisplayContainer.visibility = View.VISIBLE if (isFeedFoundBySearch) { val resId = R.string.no_feed_url_podcast_found_by_search - Snackbar.make(viewBinding.root, resId, Snackbar.LENGTH_LONG).show() + Snackbar.make(binding.root, resId, Snackbar.LENGTH_LONG).show() } - viewBinding.backgroundImage.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000) + binding.backgroundImage.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000) - viewBinding.listView.addHeaderView(headerBinding.root) - viewBinding.listView.setSelector(android.R.color.transparent) - viewBinding.listView.adapter = FeedItemlistDescriptionAdapter(this, 0, feed.items) + binding.listView.addHeaderView(hBinding.root) + binding.listView.setSelector(android.R.color.transparent) + binding.listView.adapter = OnlineItemDescriptionAdapter(this, 0, feed.items) if (StringUtils.isNotBlank(feed.imageUrl)) { Glide.with(this) @@ -409,7 +413,7 @@ class OnlineFeedViewActivity : AppCompatActivity() { .error(R.color.light_gray) .fitCenter() .dontAnimate()) - .into(viewBinding.coverImage) + .into(binding.coverImage) Glide.with(this) .load(feed.imageUrl) .apply(RequestOptions() @@ -417,14 +421,14 @@ class OnlineFeedViewActivity : AppCompatActivity() { .error(R.color.image_readability_tint) .transform(FastBlurTransformation()) .dontAnimate()) - .into(viewBinding.backgroundImage) + .into(binding.backgroundImage) } - viewBinding.titleLabel.text = feed.title - viewBinding.authorLabel.text = feed.author - headerBinding.txtvDescription.text = HtmlToPlainText.getPlainText(feed.description?:"") + binding.titleLabel.text = feed.title + binding.authorLabel.text = feed.author + hBinding.txtvDescription.text = HtmlToPlainText.getPlainText(feed.description?:"") - viewBinding.subscribeButton.setOnClickListener { + binding.subscribeButton.setOnClickListener { if (feedInFeedlist()) { openFeed() } else { @@ -434,29 +438,29 @@ class OnlineFeedViewActivity : AppCompatActivity() { } } - viewBinding.stopPreviewButton.setOnClickListener { + binding.stopPreviewButton.setOnClickListener { writeNoMediaPlaying() sendLocalBroadcast(this, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE) } if (isEnableAutodownload) { val preferences = getSharedPreferences(PREFS, MODE_PRIVATE) - viewBinding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true) + binding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true) } - headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED - headerBinding.txtvDescription.setOnClickListener { - if (headerBinding.txtvDescription.maxLines > DESCRIPTION_MAX_LINES_COLLAPSED) { - headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED + hBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED + hBinding.txtvDescription.setOnClickListener { + if (hBinding.txtvDescription.maxLines > DESCRIPTION_MAX_LINES_COLLAPSED) { + hBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED } else { - headerBinding.txtvDescription.maxLines = 2000 + hBinding.txtvDescription.maxLines = 2000 } } if (alternateFeedUrls.isEmpty()) { - viewBinding.alternateUrlsSpinner.visibility = View.GONE + binding.alternateUrlsSpinner.visibility = View.GONE } else { - viewBinding.alternateUrlsSpinner.visibility = View.VISIBLE + binding.alternateUrlsSpinner.visibility = View.VISIBLE val alternateUrlsList: MutableList = ArrayList() val alternateUrlsTitleList: MutableList = ArrayList() @@ -479,8 +483,8 @@ class OnlineFeedViewActivity : AppCompatActivity() { } adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item) - viewBinding.alternateUrlsSpinner.adapter = adapter - viewBinding.alternateUrlsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + binding.alternateUrlsSpinner.adapter = adapter + binding.alternateUrlsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) { selectedDownloadUrl = alternateUrlsList[position] } @@ -507,11 +511,11 @@ class OnlineFeedViewActivity : AppCompatActivity() { if (dli == null || selectedDownloadUrl == null) return if (dli.isDownloadingEpisode(selectedDownloadUrl!!)) { - viewBinding.subscribeButton.isEnabled = false - viewBinding.subscribeButton.setText(R.string.subscribing_label) + binding.subscribeButton.isEnabled = false + binding.subscribeButton.setText(R.string.subscribing_label) } else if (feedInFeedlist()) { - viewBinding.subscribeButton.isEnabled = true - viewBinding.subscribeButton.setText(R.string.open_podcast) + binding.subscribeButton.isEnabled = true + binding.subscribeButton.setText(R.string.open_podcast) if (didPressSubscribe) { didPressSubscribe = false @@ -519,7 +523,7 @@ class OnlineFeedViewActivity : AppCompatActivity() { val feedPreferences = feed1.preferences if (feedPreferences != null) { if (isEnableAutodownload) { - val autoDownload = viewBinding.autoDownloadCheckBox.isChecked + val autoDownload = binding.autoDownloadCheckBox.isChecked feedPreferences.autoDownload = autoDownload val preferences = getSharedPreferences(PREFS, MODE_PRIVATE) @@ -536,10 +540,10 @@ class OnlineFeedViewActivity : AppCompatActivity() { openFeed() } } else { - viewBinding.subscribeButton.isEnabled = true - viewBinding.subscribeButton.setText(R.string.subscribe_label) + binding.subscribeButton.isEnabled = true + binding.subscribeButton.setText(R.string.subscribe_label) if (isEnableAutodownload) { - viewBinding.autoDownloadCheckBox.visibility = View.VISIBLE + binding.autoDownloadCheckBox.visibility = View.VISIBLE } } } @@ -617,7 +621,7 @@ class OnlineFeedViewActivity : AppCompatActivity() { @Subscribe(threadMode = ThreadMode.MAIN) fun playbackStateChanged(event: PlayerStatusEvent?) { val isPlayingPreview = currentlyPlayingMediaType == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA.toLong() - viewBinding.stopPreviewButton.visibility = if (isPlayingPreview) View.VISIBLE else View.GONE + binding.stopPreviewButton.visibility = if (isPlayingPreview) View.VISIBLE else View.GONE } /** diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/itunes/ItunesAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineFeedsAdapter.kt similarity index 55% rename from app/src/main/java/ac/mdiq/podcini/ui/adapter/itunes/ItunesAdapter.kt rename to app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineFeedsAdapter.kt index b8669d55..499b3866 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/itunes/ItunesAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineFeedsAdapter.kt @@ -1,5 +1,8 @@ -package ac.mdiq.podcini.ui.adapter.itunes +package ac.mdiq.podcini.ui.adapter +import ac.mdiq.podcini.R +import ac.mdiq.podcini.databinding.OnlinePodcastListitemBinding +import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.ui.activity.MainActivity import android.content.Context import android.view.View @@ -7,40 +10,27 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.TextView +import androidx.media3.common.util.UnstableApi import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions -import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.ItunesPodcastListitemBinding -import ac.mdiq.podcini.net.discovery.PodcastSearchResult -import androidx.media3.common.util.UnstableApi -class ItunesAdapter( - /** - * Related Context - */ - private val context: Context, objects: List -) : ArrayAdapter(context, 0, objects) { - /** - * List holding the podcasts found in the search - */ +class OnlineFeedsAdapter(private val context: Context, objects: List) : + ArrayAdapter(context, 0, objects) { + +// List holding the podcasts found in the search private val data: List = objects @UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - //Current podcast val podcast: PodcastSearchResult = data[position] - - //ViewHolder val viewHolder: PodcastViewHolder - - //Resulting view val view: View //Handle view holder stuff if (convertView == null) { - view = (context as MainActivity).layoutInflater.inflate(R.layout.itunes_podcast_listitem, parent, false) + view = (context as MainActivity).layoutInflater.inflate(R.layout.online_podcast_listitem, parent, false) viewHolder = PodcastViewHolder(view) view.tag = viewHolder } else { @@ -50,15 +40,28 @@ class ItunesAdapter( // Set the title viewHolder.titleView.text = podcast.title - if (podcast.author != null && podcast.author.trim { it <= ' ' }.isNotEmpty()) { - viewHolder.authorView.text = podcast.author - viewHolder.authorView.visibility = View.VISIBLE - } else if (podcast.feedUrl != null && !podcast.feedUrl.contains("itunes.apple.com")) { - viewHolder.authorView.text = podcast.feedUrl - viewHolder.authorView.visibility = View.VISIBLE - } else { - viewHolder.authorView.visibility = View.GONE + when { + !podcast.author.isNullOrBlank() -> { + viewHolder.authorView.text = podcast.author.trim { it <= ' ' } + viewHolder.authorView.visibility = View.VISIBLE + } + podcast.feedUrl != null && !podcast.feedUrl.contains("itunes.apple.com") -> { + viewHolder.authorView.text = podcast.feedUrl + viewHolder.authorView.visibility = View.VISIBLE + } + else -> { + viewHolder.authorView.visibility = View.INVISIBLE + } } + viewHolder.source.text = podcast.source + ": " + podcast.feedUrl + if (podcast.count != null) { + viewHolder.countView.text = podcast.count.toString() + " episodes" + viewHolder.countView.visibility = View.VISIBLE + } else viewHolder.countView.visibility = View.INVISIBLE + if (podcast.update != null) { + viewHolder.updateView.text = podcast.update + viewHolder.updateView.visibility = View.VISIBLE + } else viewHolder.updateView.visibility = View.INVISIBLE //Update the empty imageView with the image from the feed Glide.with(context) @@ -71,25 +74,22 @@ class ItunesAdapter( .dontAnimate()) .into(viewHolder.coverView) - //Feed the grid view return view } - /** - * View holder object for the GridView - */ internal class PodcastViewHolder(view: View) { - val binding = ItunesPodcastListitemBinding.bind(view) - /** - * ImageView holding the Podcast image - */ + val binding = OnlinePodcastListitemBinding.bind(view) + val coverView: ImageView = binding.imgvCover - /** - * TextView holding the Podcast title - */ val titleView: TextView = binding.txtvTitle val authorView: TextView = binding.txtvAuthor + + val countView: TextView = binding.count + + val updateView: TextView = binding.update + + val source: TextView = binding.source } } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/FeedItemlistDescriptionAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineItemDescriptionAdapter.kt similarity index 64% rename from app/src/main/java/ac/mdiq/podcini/ui/adapter/FeedItemlistDescriptionAdapter.kt rename to app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineItemDescriptionAdapter.kt index 51ea1062..fd742f5b 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/FeedItemlistDescriptionAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/OnlineItemDescriptionAdapter.kt @@ -1,7 +1,7 @@ package ac.mdiq.podcini.ui.adapter import ac.mdiq.podcini.R -import ac.mdiq.podcini.databinding.ItemdescriptionListitemBinding +import ac.mdiq.podcini.databinding.OnlineItemDescriptionBinding import ac.mdiq.podcini.service.playback.PlaybackService.Companion.getPlayerActivityIntent import ac.mdiq.podcini.util.DateFormatter.formatAbbrev import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed @@ -24,8 +24,8 @@ import androidx.media3.common.util.UnstableApi /** * List adapter for showing a list of FeedItems with their title and description. */ -class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: List?) : - ArrayAdapter(context, resource, objects!!) { +class OnlineItemDescriptionAdapter(context: Context, resource: Int, items: List) : + ArrayAdapter(context, resource, items) { @UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { var convertView = convertView val holder: Holder @@ -36,8 +36,8 @@ class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: L if (convertView == null) { holder = Holder() val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater - convertView = inflater.inflate(R.layout.itemdescription_listitem, parent, false) - val binding = ItemdescriptionListitemBinding.bind(convertView) + convertView = inflater.inflate(R.layout.online_item_description, parent, false) + val binding = OnlineItemDescriptionBinding.bind(convertView) holder.title = binding.txtvTitle holder.pubDate = binding.txtvPubDate holder.description = binding.txtvDescription @@ -48,22 +48,21 @@ class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: L holder = convertView.tag as Holder } - holder.title!!.text = item!!.title - holder.pubDate!!.text = formatAbbrev(context, item.pubDate) + holder.title.text = item!!.title + holder.pubDate.text = formatAbbrev(context, item.pubDate) if (item.description != null) { val description = HtmlToPlainText.getPlainText(item.description!!) .replace("\n".toRegex(), " ") .replace("\\s+".toRegex(), " ") .trim { it <= ' ' } - holder.description!!.text = description - holder.description!!.maxLines = MAX_LINES_COLLAPSED + holder.description.text = description + holder.description.maxLines = MAX_LINES_COLLAPSED } - holder.description!!.tag = false - holder.preview!!.visibility = View.GONE - holder.preview!!.setOnClickListener { - if (item.media == null) { - return@setOnClickListener - } + holder.description.tag = false + holder.preview.visibility = View.GONE + holder.preview.setOnClickListener { + if (item.media == null) return@setOnClickListener + val playable: Playable = RemoteMedia(item) if (!isStreamingAllowed) { StreamingConfirmationDialog(context, playable).show() @@ -78,26 +77,26 @@ class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: L } } convertView!!.setOnClickListener { - if (holder.description!!.tag == true) { - holder.description!!.maxLines = MAX_LINES_COLLAPSED - holder.preview!!.visibility = View.GONE - holder.description!!.tag = false + if (holder.description.tag == true) { + holder.description.maxLines = MAX_LINES_COLLAPSED + holder.preview.visibility = View.GONE + holder.description.tag = false } else { - holder.description!!.maxLines = 30 - holder.description!!.tag = true + holder.description.maxLines = 30 + holder.description.tag = true - holder.preview!!.visibility = if (item.media != null) View.VISIBLE else View.GONE - holder.preview!!.setText(R.string.preview_episode) + holder.preview.visibility = if (item.media != null) View.VISIBLE else View.GONE + holder.preview.setText(R.string.preview_episode) } } return convertView } internal class Holder { - var title: TextView? = null - var pubDate: TextView? = null - var description: TextView? = null - var preview: Button? = null + lateinit var title: TextView + lateinit var pubDate: TextView + lateinit var description: TextView + lateinit var preview: Button } companion object { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/SubscriptionsRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/SubscriptionsRecyclerAdapter.kt index a6461a53..494b1f45 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/SubscriptionsRecyclerAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/SubscriptionsRecyclerAdapter.kt @@ -178,13 +178,14 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) : title.text = drawerItem.title producer.text = drawerItem.producer coverImage.contentDescription = drawerItem.title + if (drawerItem.counter > 0) { // TODO: need to use more specific number count.text = NumberFormat.getInstance().format(drawerItem.counter.toLong()) + " episodes" // count.text = NumberFormat.getInstance().format(drawerItem.feed.items.size.toLong()) + " episodes" count.visibility = View.VISIBLE } else { - count.visibility = View.GONE + count.visibility = View.VISIBLE } val mainActRef = mainActivityRef.get() ?: return diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt index b7ad6d45..4dec699c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt @@ -18,6 +18,8 @@ import ac.mdiq.podcini.databinding.FilterDialogBinding import ac.mdiq.podcini.feed.FeedItemFilterGroup import ac.mdiq.podcini.databinding.FilterDialogRowBinding import ac.mdiq.podcini.storage.model.feed.FeedItemFilter +import ac.mdiq.podcini.ui.fragment.ItemDescriptionFragment +import android.util.Log abstract class ItemFilterDialog : BottomSheetDialogFragment() { private lateinit var rows: LinearLayout @@ -27,6 +29,7 @@ abstract class ItemFilterDialog : BottomSheetDialogFragment() { val binding = FilterDialogBinding.bind(layout) rows = binding.filterRows val filter = requireArguments().getSerializable(ARGUMENT_FILTER) as FeedItemFilter? + Log.d("ItemFilterDialog", "fragment onCreateView") //add filter rows for (item in FeedItemFilterGroup.entries) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemSortDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemSortDialog.kt index c689ef99..11cad4c1 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemSortDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemSortDialog.kt @@ -18,19 +18,19 @@ import ac.mdiq.podcini.databinding.SortDialogItemBinding import ac.mdiq.podcini.storage.model.feed.SortOrder open class ItemSortDialog : BottomSheetDialogFragment() { - protected lateinit var viewBinding: SortDialogBinding + protected lateinit var binding: SortDialogBinding protected var sortOrder: SortOrder? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - viewBinding = SortDialogBinding.inflate(inflater) + binding = SortDialogBinding.inflate(inflater) populateList() - viewBinding.keepSortedCheckbox.setOnCheckedChangeListener { _: CompoundButton?, _: Boolean -> this@ItemSortDialog.onSelectionChanged() } - return viewBinding.root + binding.keepSortedCheckbox.setOnCheckedChangeListener { _: CompoundButton?, _: Boolean -> this@ItemSortDialog.onSelectionChanged() } + return binding.root } private fun populateList() { - viewBinding.gridLayout.removeAllViews() + binding.gridLayout.removeAllViews() onAddItem(R.string.episode_title, SortOrder.EPISODE_TITLE_A_Z, SortOrder.EPISODE_TITLE_Z_A, true) onAddItem(R.string.feed_title, SortOrder.FEED_TITLE_A_Z, SortOrder.FEED_TITLE_Z_A, true) onAddItem(R.string.duration, SortOrder.DURATION_SHORT_LONG, SortOrder.DURATION_LONG_SHORT, true) @@ -43,7 +43,7 @@ open class ItemSortDialog : BottomSheetDialogFragment() { protected open fun onAddItem(title: Int, ascending: SortOrder, descending: SortOrder, ascendingIsDefault: Boolean) { if (sortOrder == ascending || sortOrder == descending) { - val item = SortDialogItemActiveBinding.inflate(layoutInflater, viewBinding.gridLayout, false) + val item = SortDialogItemActiveBinding.inflate(layoutInflater, binding.gridLayout, false) val other: SortOrder when { ascending == descending -> { @@ -64,17 +64,17 @@ open class ItemSortDialog : BottomSheetDialogFragment() { populateList() onSelectionChanged() } - viewBinding.gridLayout.addView(item.root) + binding.gridLayout.addView(item.root) } else { val item = SortDialogItemBinding.inflate( - layoutInflater, viewBinding.gridLayout, false) + layoutInflater, binding.gridLayout, false) item.button.setText(title) item.button.setOnClickListener { sortOrder = if (ascendingIsDefault) ascending else descending populateList() onSelectionChanged() } - viewBinding.gridLayout.addView(item.root) + binding.gridLayout.addView(item.root) } } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/SwipeActionsDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/SwipeActionsDialog.kt index e253feb9..b262960c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/SwipeActionsDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/SwipeActionsDialog.kt @@ -68,22 +68,22 @@ class SwipeActionsDialog(private val context: Context, private val tag: String) } builder.setTitle(context.getString(R.string.swipeactions_label) + " - " + forFragment) - val viewBinding = SwipeactionsDialogBinding.inflate(LayoutInflater.from(context)) - builder.setView(viewBinding.root) + val binding = SwipeactionsDialogBinding.inflate(LayoutInflater.from(context)) + builder.setView(binding.root) - viewBinding.enableSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> - viewBinding.actionLeftContainer.root.alpha = if (b) 1.0f else 0.4f - viewBinding.actionRightContainer.root.alpha = if (b) 1.0f else 0.4f + binding.enableSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> + binding.actionLeftContainer.root.alpha = if (b) 1.0f else 0.4f + binding.actionRightContainer.root.alpha = if (b) 1.0f else 0.4f } - viewBinding.enableSwitch.isChecked = isSwipeActionEnabled(context, tag) + binding.enableSwitch.isChecked = isSwipeActionEnabled(context, tag) - setupSwipeDirectionView(viewBinding.actionLeftContainer, LEFT) - setupSwipeDirectionView(viewBinding.actionRightContainer, RIGHT) + setupSwipeDirectionView(binding.actionLeftContainer, LEFT) + setupSwipeDirectionView(binding.actionRightContainer, RIGHT) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> savePrefs(tag, rightAction!!.getId(), leftAction!!.getId()) - saveActionsEnabledPrefs(viewBinding.enableSwitch.isChecked) + saveActionsEnabledPrefs(binding.enableSwitch.isChecked) prefsChanged.onCall() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 721cd495..ec15e45f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -227,9 +227,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar @Subscribe(threadMode = ThreadMode.MAIN) fun onUnreadItemsUpdate(event: UnreadItemsUpdateEvent?) { - if (controller == null) { - return - } + if (controller == null) return updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration)) } @@ -243,13 +241,11 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar private fun setupLengthTextView() { showTimeLeft = UserPreferences.shouldShowRemainingTime() txtvLength.setOnClickListener(View.OnClickListener { - if (controller == null) { - return@OnClickListener - } + if (controller == null) return@OnClickListener + showTimeLeft = !showTimeLeft UserPreferences.setShowRemainTimeSetting(showTimeLeft) - updatePosition(PlaybackPositionEvent(controller!!.position, - controller!!.duration)) + updatePosition(PlaybackPositionEvent(controller!!.position, controller!!.duration)) }) } @@ -302,9 +298,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar private fun updateUi(media: Playable?) { if (controller != null) duration = controller!!.duration - if (media == null) { - return - } + if (media == null) return + episodeTitle.text = media.getEpisodeTitle() updatePosition(PlaybackPositionEvent(media.getPosition(), media.getDuration())) updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media))) @@ -340,14 +335,19 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar @Subscribe(threadMode = ThreadMode.MAIN) @Suppress("unused") fun bufferUpdate(event: BufferUpdateEvent) { - if (event.hasStarted()) { - progressIndicator.visibility = View.VISIBLE - } else if (event.hasEnded()) { - progressIndicator.visibility = View.GONE - } else if (controller != null && controller!!.isStreaming) { - sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt()) - } else { - sbPosition.setSecondaryProgress(0) + when { + event.hasStarted() -> { + progressIndicator.visibility = View.VISIBLE + } + event.hasEnded() -> { + progressIndicator.visibility = View.GONE + } + controller != null && controller!!.isStreaming -> { + sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt()) + } + else -> { + sbPosition.setSecondaryProgress(0) + } } } @@ -363,7 +363,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar currentChapterIndex = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), currentPosition) // Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition)); if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) { - Log.w(TAG, "Could not react to position observer update because of invalid time") + Log.w(TAG, "Could not react to position observer update because of invalid time $currentPosition $duration") return } txtvPosition.text = Converter.getDurationStringLong(currentPosition) @@ -373,14 +373,14 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar if (showTimeLeft) { txtvLength.setContentDescription(getString(R.string.remaining_time, Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong()))) - txtvLength.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime) + txtvLength.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime) } else { txtvLength.setContentDescription(getString(R.string.chapter_duration, Converter.getDurationStringLocalized(requireContext(), duration.toLong()))) txtvLength.text = Converter.getDurationStringLong(duration) } - if (!sbPosition.isPressed) { + if (!sbPosition.isPressed && event.duration > 0) { val progress: Float = (event.position.toFloat()) / event.duration sbPosition.progress = (progress * sbPosition.max).toInt() } @@ -508,7 +508,6 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar Log.d(TAG, "getItem($position)") return when (position) { -// TODO: cover page is not very useful FIRST_PAGE -> ItemDescriptionFragment() SECOND_PAGE -> CoverFragment() else -> ItemDescriptionFragment() diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/CompletedDownloadsFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/CompletedDownloadsFragment.kt index 09cd4e21..0bda171c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/CompletedDownloadsFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/CompletedDownloadsFragment.kt @@ -1,48 +1,47 @@ package ac.mdiq.podcini.ui.fragment -import ac.mdiq.podcini.ui.activity.MainActivity -import android.os.Bundle -import android.util.Log -import android.view.* -import android.widget.ProgressBar -import androidx.appcompat.widget.Toolbar -import androidx.fragment.app.Fragment -import androidx.media3.common.util.UnstableApi -import com.google.android.material.appbar.MaterialToolbar -import com.google.android.material.snackbar.Snackbar -import com.leinardi.android.speeddial.SpeedDialActionItem -import com.leinardi.android.speeddial.SpeedDialView import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding import ac.mdiq.podcini.databinding.SimpleListFragmentBinding -import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils -import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter -import ac.mdiq.podcini.ui.adapter.SelectableAdapter -import ac.mdiq.podcini.ui.adapter.actionbutton.DeleteActionButton -import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils -import ac.mdiq.podcini.storage.DBReader -import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.net.download.FeedUpdateManager -import ac.mdiq.podcini.ui.dialog.ItemSortDialog -import ac.mdiq.podcini.util.event.EpisodeDownloadEvent +import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface import ac.mdiq.podcini.playback.event.PlaybackPositionEvent -import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler -import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions -import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler +import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.storage.model.feed.SortOrder -import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface -import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.ui.activity.MainActivity +import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter +import ac.mdiq.podcini.ui.adapter.SelectableAdapter +import ac.mdiq.podcini.ui.adapter.actionbutton.DeleteActionButton +import ac.mdiq.podcini.ui.dialog.ItemSortDialog +import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler +import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions +import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler +import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.view.EmptyViewHandler import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder -import ac.mdiq.podcini.util.Converter +import ac.mdiq.podcini.util.FeedItemUtil +import ac.mdiq.podcini.util.event.EpisodeDownloadEvent import ac.mdiq.podcini.util.event.FeedItemEvent +import ac.mdiq.podcini.util.event.SwipeActionsChangedEvent +import android.os.Bundle +import android.util.Log +import android.view.* +import android.widget.ProgressBar import android.widget.TextView +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.Fragment +import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator +import com.google.android.material.appbar.MaterialToolbar +import com.google.android.material.snackbar.Snackbar +import com.leinardi.android.speeddial.SpeedDialActionItem +import com.leinardi.android.speeddial.SpeedDialView import io.reactivex.Observable import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable @@ -51,8 +50,6 @@ import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.util.* -import kotlin.collections.ArrayList -import kotlin.collections.HashSet /** * Displays all completed downloads and provides a button to delete them. @@ -61,6 +58,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis private var runningDownloads: Set? = HashSet() private var items: MutableList = mutableListOf() + private lateinit var binding: SimpleListFragmentBinding private lateinit var infoBar: TextView private lateinit var adapter: CompletedDownloadsListAdapter private lateinit var toolbar: MaterialToolbar @@ -77,7 +75,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - val binding = SimpleListFragmentBinding.inflate(inflater) + binding = SimpleListFragmentBinding.inflate(inflater) Log.d(TAG, "fragment onCreateView") toolbar = binding.toolbar @@ -101,9 +99,17 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis adapter.setOnSelectModeListener(this) recyclerView.adapter = adapter recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) + swipeActions = SwipeActions(this, TAG).attachTo(recyclerView) swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED)) + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator if (animator is SimpleItemAnimator) { animator.supportsChangeAnimations = false @@ -291,6 +297,16 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis loadItems() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) { + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + } + private fun loadItems() { disposable?.dispose() @@ -337,7 +353,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis for (item in items) { sizeMB += item.media?.size?:0 } - info += " • " + getString(R.string.size) + " : " + (sizeMB / 1000000) + " MB" + info += " • " + (sizeMB / 1000000) + " MB" } infoBar.text = info } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt index a0654084..d38a1c1e 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/DiscoveryFragment.kt @@ -8,7 +8,7 @@ import ac.mdiq.podcini.net.discovery.ItunesTopListLoader import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.ui.activity.OnlineFeedViewActivity -import ac.mdiq.podcini.ui.adapter.itunes.ItunesAdapter +import ac.mdiq.podcini.ui.adapter.OnlineFeedsAdapter import ac.mdiq.podcini.util.event.DiscoveryDefaultUpdateEvent import android.content.Context import android.content.DialogInterface @@ -50,7 +50,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { /** * Adapter responsible with the search results. */ - private var adapter: ItunesAdapter? = null + private var adapter: OnlineFeedsAdapter? = null /** * List of podcasts retreived from the search. @@ -99,7 +99,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { Log.d(TAG, "fragment onCreateView") gridView = viewBinding.gridView - adapter = ItunesAdapter(requireActivity(), ArrayList()) + adapter = OnlineFeedsAdapter(requireActivity(), ArrayList()) gridView.setAdapter(adapter) toolbar = viewBinding.toolbar diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodesListFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodesListFragment.kt index e6eb518f..d4836059 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodesListFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodesListFragment.kt @@ -58,6 +58,7 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode protected var hasMoreItems: Boolean = false private var displayUpArrow = false + lateinit var binding: EpisodesListFragmentBinding lateinit var recyclerView: EpisodeItemListRecyclerView lateinit var emptyView: EmptyViewHandler lateinit var speedDialView: SpeedDialView @@ -73,6 +74,122 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode protected var disposable: Disposable? = null protected lateinit var txtvInformation: TextView + @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + super.onCreateView(inflater, container, savedInstanceState) + + binding = EpisodesListFragmentBinding.inflate(inflater) + Log.d(TAG, "fragment onCreateView") + + txtvInformation = binding.txtvInformation + toolbar = binding.toolbar + toolbar.setOnMenuItemClickListener(this) + toolbar.setOnLongClickListener { + recyclerView.scrollToPosition(5) + recyclerView.post { recyclerView.smoothScrollToPosition(0) } + false + } + displayUpArrow = parentFragmentManager.backStackEntryCount != 0 + if (savedInstanceState != null) { + displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW) + } + (activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow) + + recyclerView = binding.recyclerView + recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool) + setupLoadMoreScrollListener() + recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar)) + + swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView) + swipeActions.setFilter(getFilter()) + + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + + val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator + if (animator is SimpleItemAnimator) { + animator.supportsChangeAnimations = false + } + + swipeRefreshLayout = binding.swipeRefresh + swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance)) + swipeRefreshLayout.setOnRefreshListener { + FeedUpdateManager.runOnceOrAsk(requireContext()) + } + + listAdapter = object : EpisodeItemListAdapter(activity as MainActivity) { + override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { + super.onCreateContextMenu(menu, v, menuInfo) +// if (!inActionMode()) { +// menu.findItem(R.id.multi_select).setVisible(true) +// } + MenuItemUtils.setOnClickListeners(menu + ) { item: MenuItem -> + this@EpisodesListFragment.onContextItemSelected(item) + } + } + } + listAdapter.setOnSelectModeListener(this) + recyclerView.adapter = listAdapter + progressBar = binding.progressBar + progressBar.visibility = View.VISIBLE + + emptyView = EmptyViewHandler(requireContext()) + emptyView.attachToRecyclerView(recyclerView) + emptyView.setIcon(R.drawable.ic_feed) + emptyView.setTitle(R.string.no_all_episodes_head_label) + emptyView.setMessage(R.string.no_all_episodes_label) + emptyView.updateAdapter(listAdapter) + emptyView.hide() + + val multiSelectDial = MultiSelectSpeedDialBinding.bind(binding.root) + speedDialView = multiSelectDial.fabSD + speedDialView.overlayLayout = multiSelectDial.fabSDOverlay + speedDialView.inflate(R.menu.episodes_apply_action_speeddial) + speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { + override fun onMainActionSelected(): Boolean { + return false + } + + override fun onToggleChanged(open: Boolean) { + if (open && listAdapter.selectedCount == 0) { + (activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected, + Snackbar.LENGTH_SHORT) + speedDialView.close() + } + } + }) + speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem -> + var confirmationString = 0 + if (listAdapter.selectedItems.size >= 25 || listAdapter.shouldSelectLazyLoadedItems()) { + // Should ask for confirmation + if (actionItem.id == R.id.mark_read_batch) { + confirmationString = R.string.multi_select_mark_played_confirmation + } else if (actionItem.id == R.id.mark_unread_batch) { + confirmationString = R.string.multi_select_mark_unplayed_confirmation + } + } + if (confirmationString == 0) { + performMultiSelectAction(actionItem.id) + } else { + object : ConfirmationDialog(activity as MainActivity, R.string.multi_select, confirmationString) { + override fun onConfirmButtonPressed(dialog: DialogInterface) { + performMultiSelectAction(actionItem.id) + } + }.createNewDialog().show() + } + true + } + + EventBus.getDefault().register(this) + loadItems() + + return binding.root + } + override fun onResume() { super.onResume() registerForContextMenu(recyclerView) @@ -126,115 +243,6 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode } } - @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - super.onCreateView(inflater, container, savedInstanceState) - - val viewBinding = EpisodesListFragmentBinding.inflate(inflater) - Log.d(TAG, "fragment onCreateView") - - txtvInformation = viewBinding.txtvInformation - toolbar = viewBinding.toolbar - toolbar.setOnMenuItemClickListener(this) - toolbar.setOnLongClickListener { - recyclerView.scrollToPosition(5) - recyclerView.post { recyclerView.smoothScrollToPosition(0) } - false - } - displayUpArrow = parentFragmentManager.backStackEntryCount != 0 - if (savedInstanceState != null) { - displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW) - } - (activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow) - - recyclerView = viewBinding.recyclerView - recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool) - setupLoadMoreScrollListener() - recyclerView.addOnScrollListener(LiftOnScrollListener(viewBinding.appbar)) - - swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView) - swipeActions.setFilter(getFilter()) - - val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator - if (animator is SimpleItemAnimator) { - animator.supportsChangeAnimations = false - } - - swipeRefreshLayout = viewBinding.swipeRefresh - swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance)) - swipeRefreshLayout.setOnRefreshListener { - FeedUpdateManager.runOnceOrAsk(requireContext()) - } - - listAdapter = object : EpisodeItemListAdapter(activity as MainActivity) { - override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { - super.onCreateContextMenu(menu, v, menuInfo) -// if (!inActionMode()) { -// menu.findItem(R.id.multi_select).setVisible(true) -// } - MenuItemUtils.setOnClickListeners(menu - ) { item: MenuItem -> - this@EpisodesListFragment.onContextItemSelected(item) - } - } - } - listAdapter.setOnSelectModeListener(this) - recyclerView.adapter = listAdapter - progressBar = viewBinding.progressBar - progressBar.visibility = View.VISIBLE - - emptyView = EmptyViewHandler(requireContext()) - emptyView.attachToRecyclerView(recyclerView) - emptyView.setIcon(R.drawable.ic_feed) - emptyView.setTitle(R.string.no_all_episodes_head_label) - emptyView.setMessage(R.string.no_all_episodes_label) - emptyView.updateAdapter(listAdapter) - emptyView.hide() - - val multiSelectDial = MultiSelectSpeedDialBinding.bind(viewBinding.root) - speedDialView = multiSelectDial.fabSD - speedDialView.overlayLayout = multiSelectDial.fabSDOverlay - speedDialView.inflate(R.menu.episodes_apply_action_speeddial) - speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { - override fun onMainActionSelected(): Boolean { - return false - } - - override fun onToggleChanged(open: Boolean) { - if (open && listAdapter.selectedCount == 0) { - (activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected, - Snackbar.LENGTH_SHORT) - speedDialView.close() - } - } - }) - speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem -> - var confirmationString = 0 - if (listAdapter.selectedItems.size >= 25 || listAdapter.shouldSelectLazyLoadedItems()) { - // Should ask for confirmation - if (actionItem.id == R.id.mark_read_batch) { - confirmationString = R.string.multi_select_mark_played_confirmation - } else if (actionItem.id == R.id.mark_unread_batch) { - confirmationString = R.string.multi_select_mark_unplayed_confirmation - } - } - if (confirmationString == 0) { - performMultiSelectAction(actionItem.id) - } else { - object : ConfirmationDialog(activity as MainActivity, R.string.multi_select, confirmationString) { - override fun onConfirmButtonPressed(dialog: DialogInterface) { - performMultiSelectAction(actionItem.id) - } - }.createNewDialog().show() - } - true - } - - EventBus.getDefault().register(this) - loadItems() - - return viewBinding.root - } - @UnstableApi private fun performMultiSelectAction(actionItemId: Int) { val handler = EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId) Completable.fromAction { @@ -379,6 +387,16 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode loadItems() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) { + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + } + fun loadItems() { disposable?.dispose() diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt index 3f4a1337..0f8bbb66 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt @@ -240,7 +240,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { if (showTimeLeft) { txtvLength.setContentDescription(getString(R.string.remaining_time, Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong()))) - txtvLength.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime) + txtvLength.text = (if (remainingTime > 0) "-" else "") + Converter.getDurationStringLong(remainingTime) } else { txtvLength.setContentDescription(getString(R.string.chapter_duration, Converter.getDurationStringLocalized(requireContext(), duration.toLong()))) @@ -289,9 +289,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { } disposable?.dispose() - disposable = Maybe.fromCallable { - controller?.getMedia() - } + disposable = Maybe.fromCallable { controller?.getMedia() } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ media: Playable? -> this.updateUi(media) }, diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt index 430cf898..b8e82dca 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt @@ -115,7 +115,15 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba adapter = FeedItemListAdapter(activity as MainActivity) adapter.setOnSelectModeListener(this) binding.recyclerView.adapter = adapter + swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView) + if (swipeActions.actions?.left != null) { + binding.header.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.header.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + binding.progressBar.visibility = View.VISIBLE val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager( @@ -399,6 +407,16 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba } } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) { + if (swipeActions.actions?.left != null) { + binding.header.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.header.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + } + @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) fun onEventMainThread(event: FeedUpdateRunningEvent) { nextPageLoader.setLoadingState(event.isFeedUpdateRunning) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt index a3362364..bbf4ecb3 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt @@ -33,7 +33,7 @@ class ItemDescriptionFragment : Fragment() { private var controller: PlaybackController? = null @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - Log.d(TAG, "Creating view") + Log.d(TAG, "fragment onCreateView") val binding = ItemDescriptionFragmentBinding.inflate(inflater) Log.d(TAG, "fragment onCreateView") diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt index e5bfd60f..7c6dc89d 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt @@ -326,9 +326,8 @@ class ItemFragment : Fragment() { } @OptIn(UnstableApi::class) private fun openPodcast() { - if (item == null) { - return - } + if (item == null) return + val fragment: Fragment = FeedItemlistFragment.newInstance(item!!.feedId) (activity as MainActivity).loadChildFragment(fragment) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt index e2732c24..c4b129fd 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt @@ -145,9 +145,8 @@ class ItemPageFragment : Fragment(), Toolbar.OnMenuItemClickListener { } @UnstableApi private fun openPodcast() { - if (item == null) { - return - } + if (item == null) return + val fragment: Fragment = FeedItemlistFragment.newInstance(item!!.feedId) (activity as MainActivity).loadChildFragment(fragment) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt index a8c1104c..2dcd0de9 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt @@ -162,9 +162,8 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener { } @UnstableApi private fun openPodcast() { - if (item == null) { - return - } + if (item == null) return + val fragment: Fragment = FeedItemlistFragment.newInstance(item!!.feedId) (activity as MainActivity).loadChildFragment(fragment) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt index 9106ff2b..c1671a73 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/NavDrawerFragment.kt @@ -65,7 +65,6 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange ): View { super.onCreateView(inflater, container, savedInstanceState) val binding = NavListBinding.inflate(inflater) -// val root: View = inflater.inflate(R.layout.nav_list, container, false) Log.d(TAG, "fragment onCreateView") setupDrawerRoundBackground(binding.root) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt index 7c5fec45..549a031c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineSearchFragment.kt @@ -18,21 +18,17 @@ import com.google.android.material.appbar.MaterialToolbar import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.FragmentItunesSearchBinding import ac.mdiq.podcini.ui.activity.OnlineFeedViewActivity -import ac.mdiq.podcini.ui.adapter.itunes.ItunesAdapter +import ac.mdiq.podcini.ui.adapter.OnlineFeedsAdapter import ac.mdiq.podcini.net.discovery.PodcastSearchResult import ac.mdiq.podcini.net.discovery.PodcastSearcher import ac.mdiq.podcini.net.discovery.PodcastSearcherRegistry import io.reactivex.disposables.Disposable -class OnlineSearchFragment -/** - * Constructor - */ - : Fragment() { +class OnlineSearchFragment : Fragment() { /** * Adapter responsible with the search results */ - private var adapter: ItunesAdapter? = null + private var adapter: OnlineFeedsAdapter? = null private var searchProvider: PodcastSearcher? = null private lateinit var gridView: GridView @@ -63,13 +59,11 @@ class OnlineSearchFragment } @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - // Inflate the layout for this fragment val binding = FragmentItunesSearchBinding.inflate(inflater) -// val root: View = inflater.inflate(R.layout.fragment_itunes_search, container, false) Log.d(TAG, "fragment onCreateView") gridView = binding.gridView - adapter = ItunesAdapter(requireContext(), ArrayList()) + adapter = OnlineFeedsAdapter(requireContext(), ArrayList()) gridView.setAdapter(adapter) //Show information about the podcast when the list item is clicked @@ -121,24 +115,26 @@ class OnlineSearchFragment toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() } val searchItem: MenuItem = toolbar.menu.findItem(R.id.action_search) - val sv = searchItem.actionView as SearchView? - sv!!.queryHint = getString(R.string.search_podcast_hint) - sv.setOnQueryTextListener(object : SearchView.OnQueryTextListener { - override fun onQueryTextSubmit(s: String): Boolean { - sv.clearFocus() - search(s) - return true - } + val sv = searchItem.actionView as? SearchView + if (sv != null) { + sv.queryHint = getString(R.string.search_podcast_hint) + sv.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(s: String): Boolean { + sv.clearFocus() + search(s) + return true + } - override fun onQueryTextChange(s: String): Boolean { - return false - } - }) - sv.setOnQueryTextFocusChangeListener(View.OnFocusChangeListener { view: View, hasFocus: Boolean -> - if (hasFocus) { - showInputMethod(view.findFocus()) - } - }) + override fun onQueryTextChange(s: String): Boolean { + return false + } + }) + sv.setOnQueryTextFocusChangeListener(View.OnFocusChangeListener { view: View, hasFocus: Boolean -> + if (hasFocus) { + showInputMethod(view.findFocus()) + } + }) + } searchItem.setOnActionExpandListener(object : MenuItem.OnActionExpandListener { override fun onMenuItemActionExpand(item: MenuItem): Boolean { return true @@ -152,7 +148,7 @@ class OnlineSearchFragment searchItem.expandActionView() if (requireArguments().getString(ARG_QUERY, null) != null) { - sv.setQuery(requireArguments().getString(ARG_QUERY, null), true) + sv?.setQuery(requireArguments().getString(ARG_QUERY, null), true) } } @@ -167,8 +163,8 @@ class OnlineSearchFragment adapter?.clear() if (searchResults != null) adapter?.addAll(searchResults!!) adapter?.notifyDataSetInvalidated() - gridView.visibility = if (searchResults!!.isNotEmpty()) View.VISIBLE else View.GONE - txtvEmpty.visibility = if (searchResults!!.isEmpty()) View.VISIBLE else View.GONE + gridView.visibility = if (!searchResults.isNullOrEmpty()) View.VISIBLE else View.GONE + txtvEmpty.visibility = if (searchResults.isNullOrEmpty()) View.VISIBLE else View.GONE txtvEmpty.text = getString(R.string.no_results_for_query) + query }, { error: Throwable -> Log.e(TAG, Log.getStackTraceString(error)) @@ -194,7 +190,7 @@ class OnlineSearchFragment } companion object { - private const val TAG = "FyydSearchFragment" + private const val TAG = "OnlineSearchFragment" private const val ARG_SEARCHER = "searcher" private const val ARG_QUERY = "query" diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt index 94e66f56..7adb2884 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt @@ -64,6 +64,7 @@ import java.util.* */ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener { + private lateinit var binding: QueueFragmentBinding private lateinit var infoBar: TextView private lateinit var recyclerView: EpisodeItemListRecyclerView private lateinit var emptyView: EmptyViewHandler @@ -88,7 +89,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { super.onCreateView(inflater, container, savedInstanceState) - val binding = QueueFragmentBinding.inflate(inflater) + binding = QueueFragmentBinding.inflate(inflater) Log.d(TAG, "fragment onCreateView") toolbar = binding.toolbar @@ -122,6 +123,13 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED)) swipeActions.attachTo(recyclerView) + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { super.onCreateContextMenu(menu, v, menuInfo) @@ -288,6 +296,16 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda refreshToolbarState() } + @Subscribe(threadMode = ThreadMode.MAIN) + fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) { + if (swipeActions.actions?.left != null) { + binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon()) + } + if (swipeActions.actions?.right != null) { + binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon()) + } + } + @Subscribe(threadMode = ThreadMode.MAIN) fun onKeyUp(event: KeyEvent) { if (!isAdded || !isVisible || !isMenuVisible) { @@ -456,7 +474,6 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda } } info += " • " - info += getString(R.string.time_left_label) info += Converter.getDurationStringLocalized(requireActivity(), timeLeft) } infoBar.text = info @@ -503,10 +520,10 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda sortOrder = UserPreferences.queueKeepSortedOrder } val view: View = super.onCreateView(inflater, container, savedInstanceState)!! - viewBinding.keepSortedCheckbox.visibility = View.VISIBLE - viewBinding.keepSortedCheckbox.setChecked(UserPreferences.isQueueKeepSorted) + binding.keepSortedCheckbox.visibility = View.VISIBLE + binding.keepSortedCheckbox.setChecked(UserPreferences.isQueueKeepSorted) // Disable until something gets selected - viewBinding.keepSortedCheckbox.setEnabled(UserPreferences.isQueueKeepSorted) + binding.keepSortedCheckbox.setEnabled(UserPreferences.isQueueKeepSorted) return view } @@ -518,11 +535,11 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @UnstableApi override fun onSelectionChanged() { super.onSelectionChanged() - viewBinding.keepSortedCheckbox.setEnabled(sortOrder != SortOrder.RANDOM) + binding.keepSortedCheckbox.setEnabled(sortOrder != SortOrder.RANDOM) if (sortOrder == SortOrder.RANDOM) { - viewBinding.keepSortedCheckbox.setChecked(false) + binding.keepSortedCheckbox.setChecked(false) } - UserPreferences.isQueueKeepSorted = viewBinding.keepSortedCheckbox.isChecked + UserPreferences.isQueueKeepSorted = binding.keepSortedCheckbox.isChecked UserPreferences.queueKeepSortedOrder = sortOrder DBWriter.reorderQueue(sortOrder, true) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/SwipePreferencesFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/SwipePreferencesFragment.kt index a7fdb527..5cef542f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/SwipePreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/SwipePreferencesFragment.kt @@ -19,13 +19,6 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() { }) true } -// findPreference(PREF_SWIPE_INBOX)?.onPreferenceClickListener = -// Preference.OnPreferenceClickListener { -// SwipeActionsDialog(requireContext(), InboxFragment.TAG).show (object : SwipeActionsDialog.Callback { -// override fun onCall() {} -// }) -// true -// } findPreference(PREF_SWIPE_EPISODES)?.onPreferenceClickListener = Preference.OnPreferenceClickListener { SwipeActionsDialog(requireContext(), AllEpisodesFragment.TAG).show (object : SwipeActionsDialog.Callback { @@ -63,7 +56,6 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() { companion object { private const val PREF_SWIPE_QUEUE = "prefSwipeQueue" -// private const val PREF_SWIPE_INBOX = "prefSwipeInbox" // private const val PREF_SWIPE_STATISTICS = "prefSwipeStatistics" private const val PREF_SWIPE_EPISODES = "prefSwipeEpisodes" private const val PREF_SWIPE_DOWNLOADS = "prefSwipeDownloads" diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/UserInterfacePreferencesFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/UserInterfacePreferencesFragment.kt index 27f186d3..69e9353b 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/UserInterfacePreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/UserInterfacePreferencesFragment.kt @@ -64,11 +64,11 @@ class UserInterfacePreferencesFragment : PreferenceFragmentCompat() { showFullNotificationButtonsDialog() true } - findPreference(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener = - (Preference.OnPreferenceClickListener { - SubscriptionsFilterDialog().show(childFragmentManager, "filter") - true - }) +// findPreference(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener = +// (Preference.OnPreferenceClickListener { +// SubscriptionsFilterDialog().show(childFragmentManager, "filter") +// true +// }) findPreference(UserPreferences.PREF_DRAWER_FEED_ORDER)?.onPreferenceClickListener = (Preference.OnPreferenceClickListener { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/NoActionSwipeAction.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/NoActionSwipeAction.kt new file mode 100644 index 00000000..d893e07f --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/NoActionSwipeAction.kt @@ -0,0 +1,35 @@ +package ac.mdiq.podcini.ui.fragment.swipeactions + +import android.content.Context +import androidx.fragment.app.Fragment +import androidx.media3.common.util.UnstableApi +import ac.mdiq.podcini.R +import ac.mdiq.podcini.storage.DBWriter +import ac.mdiq.podcini.storage.model.feed.FeedItem +import ac.mdiq.podcini.storage.model.feed.FeedItemFilter +import ac.mdiq.podcini.ui.view.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary + +class NoActionSwipeAction : SwipeAction { + override fun getId(): String { + return SwipeAction.NO_ACTION + } + + override fun getActionIcon(): Int { + return R.drawable.ic_questionmark + } + + override fun getActionColor(): Int { + return R.attr.icon_red + } + + override fun getTitle(context: Context): String { + return context.getString(R.string.no_action_label) + } + + @UnstableApi override fun performAction(item: FeedItem, fragment: Fragment, filter: FeedItemFilter) { + } + + override fun willRemove(filter: FeedItemFilter, item: FeedItem): Boolean { + return false + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeAction.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeAction.kt index b631bb3f..e52250bb 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeAction.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeAction.kt @@ -24,6 +24,8 @@ interface SwipeAction { fun willRemove(filter: FeedItemFilter, item: FeedItem): Boolean companion object { + const val NO_ACTION: String = "NO_ACTION" + const val ADD_TO_QUEUE: String = "ADD_TO_QUEUE" const val START_DOWNLOAD: String = "START_DOWNLOAD" const val MARK_FAV: String = "MARK_FAV" diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt index 1c6af85c..5c5baec3 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt @@ -17,7 +17,10 @@ import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.ui.common.ThemeUtils.getColorFromAttr import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder +import ac.mdiq.podcini.util.event.PlayerStatusEvent +import ac.mdiq.podcini.util.event.SwipeActionsChangedEvent import it.xabaras.android.recyclerview.swipedecorator.RecyclerViewSwipeDecorator +import org.greenrobot.eventbus.EventBus import java.util.* import kotlin.math.max import kotlin.math.min @@ -25,6 +28,7 @@ import kotlin.math.sin open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private val tag: String) : ItemTouchHelper.SimpleCallback(dragDirs, ItemTouchHelper.RIGHT or ItemTouchHelper.LEFT), LifecycleObserver { + private var filter: FeedItemFilter? = null var actions: Actions? = null @@ -73,6 +77,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback { override fun onCall() { this@SwipeActions.reloadPreference() + EventBus.getDefault().post(SwipeActionsChangedEvent()) } }) return @@ -147,7 +152,6 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v max(0.5, displacementPercentage.toDouble()).toFloat())) builder.create().decorate() - super.onChildDraw(c, recyclerView, viewHolder, dx, dy, actionState, isCurrentlyActive) } @@ -202,7 +206,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v } fun hasActions(): Boolean { - return right != null && left != null + return right != null && left != null && right!!.getId() != SwipeAction.NO_ACTION && left!!.getId() != SwipeAction.NO_ACTION } } @@ -213,7 +217,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v @JvmField val swipeActions: List = Collections.unmodifiableList( - listOf(AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(), + listOf(NoActionSwipeAction(), AddToQueueSwipeAction(), StartDownloadSwipeAction(), MarkFavoriteSwipeAction(), TogglePlaybackStateSwipeAction(), RemoveFromQueueSwipeAction(), DeleteSwipeAction(), RemoveFromHistorySwipeAction()) ) @@ -225,19 +229,18 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v return Actions(prefsString) } - private fun getPrefs(context: Context, tag: String): Actions { + fun getPrefs(context: Context, tag: String): Actions { return getPrefs(context, tag, "") } @JvmStatic fun getPrefsWithDefaults(context: Context, tag: String): Actions { val defaultActions = when (tag) { -// InboxFragment.TAG -> SwipeAction.ADD_TO_QUEUE + "," + SwipeAction.REMOVE_FROM_INBOX - QueueFragment.TAG -> SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE - CompletedDownloadsFragment.TAG -> SwipeAction.DELETE + "," + SwipeAction.DELETE - PlaybackHistoryFragment.TAG -> SwipeAction.REMOVE_FROM_HISTORY + "," + SwipeAction.REMOVE_FROM_HISTORY - AllEpisodesFragment.TAG -> SwipeAction.MARK_FAV + "," + SwipeAction.START_DOWNLOAD - else -> SwipeAction.MARK_FAV + "," + SwipeAction.START_DOWNLOAD + QueueFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION + CompletedDownloadsFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION + PlaybackHistoryFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION + AllEpisodesFragment.TAG -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION + else -> SwipeAction.NO_ACTION + "," + SwipeAction.NO_ACTION } return getPrefs(context, tag, defaultActions) } diff --git a/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt b/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt index 4ea3ed63..4efb6f88 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/ChapterUtils.kt @@ -118,21 +118,23 @@ object ChapterUtils { if (playable.getLocalMediaUrl() == null) { throw IOException("No local url") } - val source = File(playable.getLocalMediaUrl()?:"") + val source = File(playable.getLocalMediaUrl() ?: "") if (!source.exists()) { throw IOException("Local file does not exist") } return CountingInputStream(BufferedInputStream(FileInputStream(source))) - } else if (playable.getStreamUrl() != null && playable.getStreamUrl()!!.startsWith(ContentResolver.SCHEME_CONTENT)) { - val uri = Uri.parse(playable.getStreamUrl()) - return CountingInputStream(BufferedInputStream(context.contentResolver.openInputStream(uri))) } else { - val request: Request = Builder().url(playable.getStreamUrl()?:"").build() - val response = getHttpClient().newCall(request).execute() - if (response.body == null) { - throw IOException("Body is null") + val streamurl = playable.getStreamUrl() + if (streamurl != null && streamurl.startsWith(ContentResolver.SCHEME_CONTENT)) { + val uri = Uri.parse(streamurl) + return CountingInputStream(BufferedInputStream(context.contentResolver.openInputStream(uri))) + } else { + if (streamurl.isNullOrEmpty()) throw IOException("stream url is null of empty") + val request: Request = Builder().url(streamurl).build() + val response = getHttpClient().newCall(request).execute() + if (response.body == null) throw IOException("Body is null") + return CountingInputStream(BufferedInputStream(response.body!!.byteStream())) } - return CountingInputStream(BufferedInputStream(response.body!!.byteStream())) } } diff --git a/app/src/main/java/ac/mdiq/podcini/util/event/SwipeActionsChangedEvent.kt b/app/src/main/java/ac/mdiq/podcini/util/event/SwipeActionsChangedEvent.kt new file mode 100644 index 00000000..94e89048 --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/util/event/SwipeActionsChangedEvent.kt @@ -0,0 +1,3 @@ +package ac.mdiq.podcini.util.event + +class SwipeActionsChangedEvent diff --git a/app/src/main/res/drawable/baseline_arrow_left_alt_24.xml b/app/src/main/res/drawable/baseline_arrow_left_alt_24.xml new file mode 100644 index 00000000..f44b1280 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_left_alt_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_arrow_right_alt_24.xml b/app/src/main/res/drawable/baseline_arrow_right_alt_24.xml new file mode 100644 index 00000000..04dc4f99 --- /dev/null +++ b/app/src/main/res/drawable/baseline_arrow_right_alt_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/baseline_arrow_upward_24.xml b/app/src/main/res/drawable/baseline_arrow_upward_24.xml index 2b2c71a3..32bad393 100644 --- a/app/src/main/res/drawable/baseline_arrow_upward_24.xml +++ b/app/src/main/res/drawable/baseline_arrow_upward_24.xml @@ -1,5 +1,6 @@ - + diff --git a/app/src/main/res/layout/episodes_list_fragment.xml b/app/src/main/res/layout/episodes_list_fragment.xml index e0596601..748ff25f 100644 --- a/app/src/main/res/layout/episodes_list_fragment.xml +++ b/app/src/main/res/layout/episodes_list_fragment.xml @@ -20,18 +20,60 @@ app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationIcon="?homeAsUpIndicator" /> - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/feeditem_page_fragment.xml b/app/src/main/res/layout/feeditem_page_fragment.xml index 1a0c6c89..0ab96a57 100644 --- a/app/src/main/res/layout/feeditem_page_fragment.xml +++ b/app/src/main/res/layout/feeditem_page_fragment.xml @@ -21,7 +21,6 @@ android:id="@+id/fragment_view" android:layout_width="match_parent" android:layout_height="match_parent" - android:foreground="?android:windowContentOverlay" - /> + android:foreground="?android:windowContentOverlay"/> diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml index 01717b8e..247820cf 100644 --- a/app/src/main/res/layout/feeditemlist_header.xml +++ b/app/src/main/res/layout/feeditemlist_header.xml @@ -152,18 +152,61 @@ tools:visibility="visible" tools:text="(!) Last refresh failed" /> - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + + tools:listitem="@layout/online_podcast_listitem" /> - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/itemdescription_listitem.xml b/app/src/main/res/layout/online_item_description.xml similarity index 97% rename from app/src/main/res/layout/itemdescription_listitem.xml rename to app/src/main/res/layout/online_item_description.xml index 04d35fb2..205fa38a 100644 --- a/app/src/main/res/layout/itemdescription_listitem.xml +++ b/app/src/main/res/layout/online_item_description.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:id="@+id/itemdescription_listitem" + android:id="@+id/online_item_description" android:orientation="vertical" android:paddingHorizontal="20dp" android:paddingVertical="16dp"> diff --git a/app/src/main/res/layout/online_podcast_listitem.xml b/app/src/main/res/layout/online_podcast_listitem.xml new file mode 100644 index 00000000..c4b69b1e --- /dev/null +++ b/app/src/main/res/layout/online_podcast_listitem.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/onlinefeedview_activity.xml b/app/src/main/res/layout/onlinefeedview_activity.xml index 88b87da1..09d2ce03 100644 --- a/app/src/main/res/layout/onlinefeedview_activity.xml +++ b/app/src/main/res/layout/onlinefeedview_activity.xml @@ -161,7 +161,6 @@ android:text="@string/stop_preview" android:visibility="gone" tools:visibility="visible" /> - - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/simple_list_fragment.xml b/app/src/main/res/layout/simple_list_fragment.xml index 6678590d..1888422a 100644 --- a/app/src/main/res/layout/simple_list_fragment.xml +++ b/app/src/main/res/layout/simple_list_fragment.xml @@ -19,17 +19,58 @@ app:navigationContentDescription="@string/toolbar_back_button_content_description" app:navigationIcon="?homeAsUpIndicator" /> - + android:orientation="horizontal"> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml index a99151ac..c7cae491 100644 --- a/app/src/main/res/menu/mediaplayer.xml +++ b/app/src/main/res/menu/mediaplayer.xml @@ -45,7 +45,7 @@ diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1c0a5045..1af6847d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -10,7 +10,7 @@ 14sp 16sp 20sp - 56dp + 65dp 56dp 92dp 132dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e074370..fda2d144 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -217,6 +217,8 @@ %d downloaded episodes deleted. + No action + Removed from inbox Mark as played Toggle played state diff --git a/app/src/main/res/xml/preferences_swipe.xml b/app/src/main/res/xml/preferences_swipe.xml index 04231189..43bef5ab 100644 --- a/app/src/main/res/xml/preferences_swipe.xml +++ b/app/src/main/res/xml/preferences_swipe.xml @@ -5,10 +5,6 @@ android:key="prefSwipeQueue" android:title="@string/queue_label"/> - - - - @@ -21,12 +17,12 @@ android:key="prefSwipeHistory" android:title="@string/playback_history_label"/> - + + + - + + + diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml index 44c32604..b6afa89a 100644 --- a/app/src/main/res/xml/preferences_user_interface.xml +++ b/app/src/main/res/xml/preferences_user_interface.xml @@ -45,10 +45,10 @@ android:key="prefDrawerFeedIndicator" android:summary="@string/pref_nav_drawer_feed_counter_sum" android:defaultValue="1"/> - + + + + diff --git a/changelog.md b/changelog.md index 7fddde42..03b2000e 100644 --- a/changelog.md +++ b/changelog.md @@ -93,4 +93,15 @@ * renewed PodcastIndex API keys * added share notes menu option in episode view * press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe -* press on the icon of an episode opens the episode info the original way - with horizontal swipe \ No newline at end of file +* press on the icon of an episode opens the episode info the original way - with horizontal swipe + +## 4.3.0 + +* added more info about feeds in the online search view +* fixed bug of preview not playing +* disabled feed filters setting in preference +* "open feed" is an action item on audio player top bar +* added swipe action telltales in all episode lists +* added NO_ACTION swipe action +* all default swipe actions are set to NO_ACTION +* cleaned up swipe preferences ui: statistics and individual subs removed \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/3020111.txt b/fastlane/metadata/android/en-US/changelogs/3020111.txt new file mode 100644 index 00000000..9e5b4d0c --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020111.txt @@ -0,0 +1,11 @@ + +Version 4.3.0 brings several changes: + +* added more info about feeds in the online search view +* fixed bug of preview not playing +* disabled feed filters setting in preference +* "open feed" is an action item on audio player top bar +* added swipe action telltales in all episode lists +* added NO_ACTION swipe action +* all default swipe actions are set to NO_ACTION +* cleaned up swipe preferences ui: statistics and individual subs removed diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/10_player.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/10_player.jpg deleted file mode 100644 index ec3dc6a5..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/10_player.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_drawer.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_drawer.jpg index b7fa0f56..fa57ef74 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_drawer.jpg and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_drawer.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting.jpg index a3804756..733c1859 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting.jpg and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting.jpg differ diff --git a/images/3_setting.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting1.jpg similarity index 100% rename from images/3_setting.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/2_setting1.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_setting.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_setting.jpg deleted file mode 100644 index ffcfe8e6..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3_setting.jpg and /dev/null differ diff --git a/images/4_subscriptions.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/3_subscriptions.jpg similarity index 100% rename from images/4_subscriptions.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/3_subscriptions.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_queue.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_queue.jpg new file mode 100644 index 00000000..49305f1b Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_queue.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_subscriptions.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/4_subscriptions.jpg deleted file mode 100644 index 88dfd336..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4_subscriptions.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_podcast.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_podcast.jpg new file mode 100644 index 00000000..74959cc5 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_podcast.jpg differ diff --git a/images/7_podcast.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_podcast1.jpg similarity index 100% rename from images/7_podcast.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/5_podcast1.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_queue.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_queue.jpg deleted file mode 100644 index 55893b4d..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_queue.jpg and /dev/null differ diff --git a/images/8_episode.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_episode.jpg similarity index 100% rename from images/8_episode.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/6_episode.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_podcast.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_podcast.jpg deleted file mode 100644 index 53b150de..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_podcast.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7_podcast.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/7_podcast.jpg deleted file mode 100644 index b9788611..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/7_podcast.jpg and /dev/null differ diff --git a/images/9_speed.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/7_speed.jpg similarity index 100% rename from images/9_speed.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/7_speed.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8_episode.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/8_episode.jpg deleted file mode 100644 index f8e81f9c..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/8_episode.jpg and /dev/null differ diff --git a/images/10_player.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/8_player.jpg similarity index 100% rename from images/10_player.jpg rename to fastlane/metadata/android/en-US/images/phoneScreenshots/8_player.jpg diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/91_feed_search.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/91_feed_search.jpg new file mode 100644 index 00000000..6af26191 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/91_feed_search.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/9_speed.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/9_speed.jpg deleted file mode 100644 index 0d372337..00000000 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/9_speed.jpg and /dev/null differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting.jpg new file mode 100644 index 00000000..fac5b83a Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting1.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting1.jpg new file mode 100644 index 00000000..24e666a9 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/9_swipe_setting1.jpg differ diff --git a/images/1_drawer1.jpg b/images/1_drawer.jpg similarity index 100% rename from images/1_drawer1.jpg rename to images/1_drawer.jpg diff --git a/images/2_setting1.jpg b/images/2_setting1.jpg new file mode 100644 index 00000000..0797edc4 Binary files /dev/null and b/images/2_setting1.jpg differ diff --git a/images/3_subscriptions.jpg b/images/3_subscriptions.jpg new file mode 100644 index 00000000..255d2e46 Binary files /dev/null and b/images/3_subscriptions.jpg differ diff --git a/images/4_queue.jpg b/images/4_queue.jpg new file mode 100644 index 00000000..49305f1b Binary files /dev/null and b/images/4_queue.jpg differ diff --git a/images/5_podcast.jpg b/images/5_podcast.jpg new file mode 100644 index 00000000..74959cc5 Binary files /dev/null and b/images/5_podcast.jpg differ diff --git a/images/5_podcast1.jpg b/images/5_podcast1.jpg new file mode 100644 index 00000000..3b6a1f16 Binary files /dev/null and b/images/5_podcast1.jpg differ diff --git a/images/5_queue.jpg b/images/5_queue.jpg deleted file mode 100644 index 7163713a..00000000 Binary files a/images/5_queue.jpg and /dev/null differ diff --git a/images/6_episode.jpg b/images/6_episode.jpg new file mode 100644 index 00000000..900a7f5a Binary files /dev/null and b/images/6_episode.jpg differ diff --git a/images/6_podcast.jpg b/images/6_podcast.jpg deleted file mode 100644 index ccf0dbbd..00000000 Binary files a/images/6_podcast.jpg and /dev/null differ diff --git a/images/7_speed.jpg b/images/7_speed.jpg new file mode 100644 index 00000000..597d145a Binary files /dev/null and b/images/7_speed.jpg differ diff --git a/images/8_player.jpg b/images/8_player.jpg new file mode 100644 index 00000000..5de97d4e Binary files /dev/null and b/images/8_player.jpg differ diff --git a/images/91_feed_search.jpg b/images/91_feed_search.jpg new file mode 100644 index 00000000..6af26191 Binary files /dev/null and b/images/91_feed_search.jpg differ diff --git a/images/9_swipe_setting.jpg b/images/9_swipe_setting.jpg new file mode 100644 index 00000000..fac5b83a Binary files /dev/null and b/images/9_swipe_setting.jpg differ diff --git a/images/9_swipe_setting1.jpg b/images/9_swipe_setting1.jpg new file mode 100644 index 00000000..24e666a9 Binary files /dev/null and b/images/9_swipe_setting1.jpg differ