feed search and swipe telltales etc
10
README.md
|
@ -14,13 +14,15 @@ Version 4.1 brings a more convenient player control and tags enhancements, while
|
|||
|
||||
## Screenshots
|
||||
|
||||
<img src="./images/1_drawer1.jpg" width="238" /> <img src="./images/2_setting.jpg" width="238" /> <img src="./images/3_setting.jpg" width="238" />
|
||||
<img src="./images/1_drawer.jpg" width="238" /> <img src="./images/2_setting.jpg" width="238" /> <img src="./images/2_setting1.jpg" width="238" />
|
||||
|
||||
<img src="./images/4_subscriptions.jpg" width="238" /> <img src="./images/5_queue.jpg" width="238" />
|
||||
<img src="./images/3_subscriptions.jpg" width="238" /> <img src="./images/4_queue.jpg" width="238" />
|
||||
|
||||
<img src="./images/6_podcast.jpg" width="238" /> <img src="./images/7_podcast.jpg" width="238" /> <img src="./images/8_episode.jpg" width="238" />
|
||||
<img src="./images/5_podcast.jpg" width="238" /> <img src="./images/5_podcast1.jpg" width="238" /> <img src="./images/6_episode.jpg" width="238" />
|
||||
|
||||
<img src="./images/9_speed.jpg" width="238" /> <img src="./images/10_player.jpg" width="238" />
|
||||
<img src="./images/7_speed.jpg" width="238" /> <img src="./images/8_player.jpg" width="238" />
|
||||
|
||||
<img src="./images/9_swipe_setting.jpg" width="238" /> <img src="./images/9_swipe_setting1.jpg" width="238" /> <img src="./images/91_feed_search.jpg" width="238" />
|
||||
|
||||
## Changelogs
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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, "")
|
||||
|
|
|
@ -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<Int>?) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<FeedItem?>
|
||||
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<Int, Int>?
|
||||
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
|
||||
|
|
|
@ -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!!))
|
||||
|
|
|
@ -867,7 +867,7 @@ object DBReader {
|
|||
// getFeedList(adapter)
|
||||
|
||||
// TODO:
|
||||
if (false || subscriptionsFilter != null) {
|
||||
if (false && subscriptionsFilter != null) {
|
||||
feeds = subscriptionsFilter.filter(feeds, feedCounters as Map<Long?, Int>).toMutableList()
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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? {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<String, String>) {
|
||||
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<String> = ArrayList()
|
||||
val alternateUrlsTitleList: MutableList<String?> = 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
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<PodcastSearchResult>
|
||||
) : ArrayAdapter<PodcastSearchResult?>(context, 0, objects) {
|
||||
/**
|
||||
* List holding the podcasts found in the search
|
||||
*/
|
||||
class OnlineFeedsAdapter(private val context: Context, objects: List<PodcastSearchResult>) :
|
||||
ArrayAdapter<PodcastSearchResult?>(context, 0, objects) {
|
||||
|
||||
// List holding the podcasts found in the search
|
||||
private val data: List<PodcastSearchResult> = 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
|
||||
}
|
||||
}
|
|
@ -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<FeedItem?>?) :
|
||||
ArrayAdapter<FeedItem?>(context, resource, objects!!) {
|
||||
class OnlineItemDescriptionAdapter(context: Context, resource: Int, items: List<FeedItem>) :
|
||||
ArrayAdapter<FeedItem>(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 {
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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<String>? = HashSet()
|
||||
private var items: MutableList<FeedItem> = 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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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<Playable?> {
|
||||
controller?.getMedia()
|
||||
}
|
||||
disposable = Maybe.fromCallable<Playable?> { controller?.getMedia() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ media: Playable? -> this.updateUi(media) },
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -19,13 +19,6 @@ class SwipePreferencesFragment : PreferenceFragmentCompat() {
|
|||
})
|
||||
true
|
||||
}
|
||||
// findPreference<Preference>(PREF_SWIPE_INBOX)?.onPreferenceClickListener =
|
||||
// Preference.OnPreferenceClickListener {
|
||||
// SwipeActionsDialog(requireContext(), InboxFragment.TAG).show (object : SwipeActionsDialog.Callback {
|
||||
// override fun onCall() {}
|
||||
// })
|
||||
// true
|
||||
// }
|
||||
findPreference<Preference>(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"
|
||||
|
|
|
@ -64,11 +64,11 @@ class UserInterfacePreferencesFragment : PreferenceFragmentCompat() {
|
|||
showFullNotificationButtonsDialog()
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener =
|
||||
(Preference.OnPreferenceClickListener {
|
||||
SubscriptionsFilterDialog().show(childFragmentManager, "filter")
|
||||
true
|
||||
})
|
||||
// findPreference<Preference>(UserPreferences.PREF_FILTER_FEED)?.onPreferenceClickListener =
|
||||
// (Preference.OnPreferenceClickListener {
|
||||
// SubscriptionsFilterDialog().show(childFragmentManager, "filter")
|
||||
// true
|
||||
// })
|
||||
|
||||
findPreference<Preference>(UserPreferences.PREF_DRAWER_FEED_ORDER)?.onPreferenceClickListener =
|
||||
(Preference.OnPreferenceClickListener {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<SwipeAction> = 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)
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
class SwipeActionsChangedEvent
|
|
@ -0,0 +1,9 @@
|
|||
<vector android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M3.99,12l3.99,-4v3H20v2H7.99v3z"/>
|
||||
</vector>
|
|
@ -0,0 +1,9 @@
|
|||
<vector android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M16.01,11H4v2h12.01v3L20,12l-3.99,-4z"/>
|
||||
</vector>
|
|
@ -1,5 +1,6 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color" android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
|
||||
<path android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M4,12l1.41,1.41L11,7.83V20h2V7.83l5.58,5.59L20,12l-8,-8 -8,8z"/>
|
||||
</vector>
|
||||
|
|
|
@ -20,18 +20,60 @@
|
|||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||
app:navigationIcon="?homeAsUpIndicator" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:paddingBottom="8dp"
|
||||
android:text="@string/filtered_label"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="60dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/left_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_left_alt_24" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:text="@string/filtered_label"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_right_alt_24" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/right_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -152,18 +152,61 @@
|
|||
tools:visibility="visible"
|
||||
tools:text="(!) Last refresh failed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
android:textColor="?attr/colorAccent"
|
||||
tools:visibility="visible"
|
||||
tools:text="(i) Information" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/left_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_left_alt_24" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvInformation"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:foreground="?android:attr/selectableItemBackground"
|
||||
android:visibility="gone"
|
||||
android:gravity="center"
|
||||
android:textColor="?attr/colorAccent"
|
||||
tools:visibility="visible"
|
||||
tools:text="(i) Information" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_right_alt_24" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/right_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvUpdatesDisabled"
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
android:paddingTop="@dimen/list_vertical_padding"
|
||||
android:stretchMode="columnWidth"
|
||||
android:verticalSpacing="8dp"
|
||||
tools:listitem="@layout/itunes_podcast_listitem" />
|
||||
tools:listitem="@layout/online_podcast_listitem" />
|
||||
|
||||
<TextView
|
||||
android:id="@android:id/empty"
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/itunes_podcast_listitem"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
tools:background="@android:color/darker_gray">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_height="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:adjustViewBounds="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:cropToPadding="true"
|
||||
android:scaleType="fitXY"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:layout_toEndOf="@id/imgvCover"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Podcini.TextView.ListItemPrimaryTitle"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
tools:text="Podcast title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvAuthor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
style="android:style/TextAppearance.Small"
|
||||
tools:text="author"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -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">
|
|
@ -0,0 +1,110 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/online_podcast_listitem"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="8dp"
|
||||
tools:background="@android:color/darker_gray">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:paddingBottom="4dp"
|
||||
style="@style/Podcini.TextView.ListItemPrimaryTitle"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
tools:text="Podcast title" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_height="@dimen/thumbnail_length_itemlist"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:adjustViewBounds="true"
|
||||
android:importantForAccessibility="no"
|
||||
android:cropToPadding="true"
|
||||
android:scaleType="fitXY"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:layout_toRightOf="@id/imgvCover"
|
||||
android:layout_toEndOf="@id/imgvCover"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvAuthor"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:ellipsize="middle"
|
||||
style="android:style/TextAppearance.Small"
|
||||
tools:text="author"
|
||||
tools:background="@android:color/holo_green_dark"
|
||||
android:singleLine="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="1"
|
||||
style="android:style/TextAppearance.Small"
|
||||
tools:text="10 episodes"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/update"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:maxLines="1"
|
||||
style="android:style/TextAppearance.Small"
|
||||
tools:text="2023-03-04"
|
||||
tools:background="@android:color/holo_green_dark" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/source"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="11sp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:ellipsize="middle"
|
||||
android:maxLines="2"
|
||||
style="android:style/TextAppearance.Small"
|
||||
tools:text="directory: http://address"
|
||||
tools:background="@android:color/holo_green_dark"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -161,7 +161,6 @@
|
|||
android:text="@string/stop_preview"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ListView
|
||||
|
|
|
@ -22,17 +22,58 @@
|
|||
app:navigationIcon="?homeAsUpIndicator"
|
||||
app:title="@string/queue_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/info_bar"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="60dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
tools:text="12 Episodes - Time remaining: 12 hours" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/left_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_left_alt_24" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/info_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:text="12 Episodes - 12 hours" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_right_alt_24" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/right_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
|
@ -19,17 +19,58 @@
|
|||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||
app:navigationIcon="?homeAsUpIndicator" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/info_bar"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="-12dp"
|
||||
android:layout_marginLeft="60dp"
|
||||
android:layout_marginStart="60dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
tools:text="12 Episodes - Size: 40 MB" />
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/left_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_left_alt_24" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/info_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:layout_gravity="center_vertical"
|
||||
tools:text="12 Episodes - 40 MB" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/baseline_arrow_right_alt_24" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:id="@+id/right_action_icon"
|
||||
android:scaleType="fitXY"
|
||||
app:srcCompat="@drawable/ic_questionmark" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
<item
|
||||
android:id="@+id/open_feed_item"
|
||||
android:icon="@drawable/ic_feed"
|
||||
custom:showAsAction="collapseActionView"
|
||||
custom:showAsAction="always"
|
||||
android:title="@string/open_podcast"
|
||||
android:visible="false">
|
||||
</item>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<dimen name="text_size_small">14sp</dimen>
|
||||
<dimen name="text_size_navdrawer">16sp</dimen>
|
||||
<dimen name="text_size_large">20sp</dimen>
|
||||
<dimen name="thumbnail_length_itemlist">56dp</dimen>
|
||||
<dimen name="thumbnail_length_itemlist">65dp</dimen>
|
||||
<dimen name="thumbnail_length_queue_item">56dp</dimen>
|
||||
<dimen name="thumbnail_length_onlinefeedview">92dp</dimen>
|
||||
<dimen name="feeditemlist_header_height">132dp</dimen>
|
||||
|
|
|
@ -217,6 +217,8 @@
|
|||
<item quantity="other">%d downloaded episodes deleted.</item>
|
||||
</plurals>
|
||||
|
||||
<string name="no_action_label">No action</string>
|
||||
|
||||
<string name="removed_inbox_label">Removed from inbox</string>
|
||||
<string name="mark_read_label">Mark as played</string>
|
||||
<string name="toggle_played_label">Toggle played state</string>
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
android:key="prefSwipeQueue"
|
||||
android:title="@string/queue_label"/>
|
||||
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="prefSwipeInbox"-->
|
||||
<!-- android:title="@string/inbox_label"/>-->
|
||||
|
||||
<Preference
|
||||
android:key="prefSwipeEpisodes"
|
||||
android:title="@string/episodes_label"/>
|
||||
|
@ -21,12 +17,12 @@
|
|||
android:key="prefSwipeHistory"
|
||||
android:title="@string/playback_history_label"/>
|
||||
|
||||
<Preference
|
||||
android:key="prefSwipeStatistics"
|
||||
android:title="@string/statistics_label"/>
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="prefSwipeStatistics"-->
|
||||
<!-- android:title="@string/statistics_label"/>-->
|
||||
|
||||
<Preference
|
||||
android:key="prefSwipeFeed"
|
||||
android:title="@string/individual_subscription"/>
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="prefSwipeFeed"-->
|
||||
<!-- android:title="@string/individual_subscription"/>-->
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -45,10 +45,10 @@
|
|||
android:key="prefDrawerFeedIndicator"
|
||||
android:summary="@string/pref_nav_drawer_feed_counter_sum"
|
||||
android:defaultValue="1"/>
|
||||
<Preference
|
||||
android:title="@string/pref_filter_feed_title"
|
||||
android:key="prefSubscriptionsFilter"
|
||||
android:summary="@string/pref_filter_feed_sum" />
|
||||
<!-- <Preference-->
|
||||
<!-- android:title="@string/pref_filter_feed_title"-->
|
||||
<!-- android:key="prefSubscriptionsFilter"-->
|
||||
<!-- android:summary="@string/pref_filter_feed_sum" />-->
|
||||
<!-- <SwitchPreferenceCompat-->
|
||||
<!-- android:title="@string/pref_show_subscription_title"-->
|
||||
<!-- android:key="prefSubscriptionTitle"-->
|
||||
|
|
13
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
|
||||
* 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
|
|
@ -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
|
Before Width: | Height: | Size: 198 KiB |
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 189 KiB |
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 177 KiB |
Before Width: | Height: | Size: 203 KiB |
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB |
After Width: | Height: | Size: 291 KiB |
Before Width: | Height: | Size: 271 KiB |
After Width: | Height: | Size: 335 KiB |
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 326 KiB |
Before Width: | Height: | Size: 287 KiB After Width: | Height: | Size: 287 KiB |
Before Width: | Height: | Size: 321 KiB |
Before Width: | Height: | Size: 230 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 342 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 320 KiB |
Before Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 159 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 177 KiB |
After Width: | Height: | Size: 221 KiB |
After Width: | Height: | Size: 291 KiB |
After Width: | Height: | Size: 335 KiB |
After Width: | Height: | Size: 188 KiB |
Before Width: | Height: | Size: 273 KiB |
After Width: | Height: | Size: 287 KiB |
Before Width: | Height: | Size: 258 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 170 KiB |
After Width: | Height: | Size: 320 KiB |
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 159 KiB |