6.15.1 commit
This commit is contained in:
parent
c2a6c71e18
commit
f2f4b8dcee
|
@ -26,8 +26,8 @@ android {
|
|||
vectorDrawables.useSupportLibrary false
|
||||
vectorDrawables.generatedDensities = []
|
||||
|
||||
versionCode 3020308
|
||||
versionName "6.15.0"
|
||||
versionCode 3020309
|
||||
versionName "6.15.1"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
|
|
@ -92,18 +92,6 @@
|
|||
<meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE" android:value="true" />
|
||||
</service>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.PlaybackSpeedDialogActivity"
|
||||
android:noHistory="true"
|
||||
android:exported="false"
|
||||
android:excludeFromRecents="true"
|
||||
android:theme="@style/Theme.Podcini.Light.Translucent">
|
||||
<intent-filter>
|
||||
<action android:name="ac.mdiq.podcini.intents.PLAYBACK_SPEED" />
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut"
|
||||
android:value="true"/>
|
||||
<meta-data android:name="com.google.android.gms.car.notification.SmallIcon"
|
||||
|
@ -168,28 +156,6 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.WidgetConfigActivity"
|
||||
android:label="@string/widget_settings"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGUR"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.PlayerWidget"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
<action android:name="ac.mdiq.podcini.FORCE_WIDGET_UPDATE"/>
|
||||
<action android:name="ac.mdiq.podcini.STOP_WIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/player_widget_info"/>
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.BugReportActivity"
|
||||
android:label="@string/bug_report_title">
|
||||
|
|
|
@ -27,7 +27,6 @@ import kotlinx.coroutines.launch
|
|||
* Communicates with the playback service. GUI classes should use this class to
|
||||
* control playback instead of communicating with the PlaybackService directly.
|
||||
*/
|
||||
|
||||
abstract class ServiceStatusHandler(private val activity: FragmentActivity) {
|
||||
|
||||
private var mediaInfoLoaded = false
|
||||
|
@ -150,8 +149,7 @@ abstract class ServiceStatusHandler(private val activity: FragmentActivity) {
|
|||
when (MediaPlayerBase.status) {
|
||||
PlayerStatus.PLAYING -> updatePlayButton(false)
|
||||
PlayerStatus.PREPARING -> updatePlayButton(!PlaybackService.isStartWhenPrepared)
|
||||
PlayerStatus.FALLBACK, PlayerStatus.PAUSED, PlayerStatus.PREPARED, PlayerStatus.STOPPED, PlayerStatus.INITIALIZED ->
|
||||
updatePlayButton(true)
|
||||
PlayerStatus.FALLBACK, PlayerStatus.PAUSED, PlayerStatus.PREPARED, PlayerStatus.STOPPED, PlayerStatus.INITIALIZED -> updatePlayButton(true)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,9 +46,6 @@ import java.util.concurrent.CountDownLatch
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.Throws
|
||||
|
||||
/**
|
||||
* Manages the MediaPlayer object of the PlaybackService.
|
||||
*/
|
||||
class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaPlayerBase(context, callback) {
|
||||
|
||||
@Volatile
|
||||
|
@ -84,14 +81,10 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
}
|
||||
|
||||
private val videoWidth: Int
|
||||
get() {
|
||||
return exoPlayer?.videoFormat?.width ?: 0
|
||||
}
|
||||
get() = exoPlayer?.videoFormat?.width ?: 0
|
||||
|
||||
private val videoHeight: Int
|
||||
get() {
|
||||
return exoPlayer?.videoFormat?.height ?: 0
|
||||
}
|
||||
get() = exoPlayer?.videoFormat?.height ?: 0
|
||||
|
||||
init {
|
||||
if (httpDataSourceFactory == null) {
|
||||
|
@ -137,15 +130,6 @@ class LocalMediaPlayer(context: Context, callback: MediaPlayerCallback) : MediaP
|
|||
bufferingUpdateListener = null
|
||||
}
|
||||
|
||||
// private fun setAudioStreamType(i: Int) {
|
||||
// val a = exoPlayer!!.audioAttributes
|
||||
// val b = AudioAttributes.Builder()
|
||||
// b.setContentType(i)
|
||||
// b.setFlags(a.flags)
|
||||
// b.setUsage(a.usage)
|
||||
// exoPlayer?.setAudioAttributes(b.build(), true)
|
||||
// }
|
||||
|
||||
/**
|
||||
* Starts or prepares playback of the specified Playable object. If another Playable object is already being played, the currently playing
|
||||
* episode will be stopped and replaced with the new Playable object. If the Playable object is already being played, the method will not do anything.
|
||||
|
|
|
@ -30,11 +30,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DataSpec
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
|
@ -44,6 +41,7 @@ import androidx.media3.extractor.DefaultExtractorsFactory
|
|||
import androidx.media3.extractor.mp3.Mp3Extractor
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import kotlin.Throws
|
||||
import kotlin.math.max
|
||||
|
||||
/*
|
||||
|
|
|
@ -56,8 +56,6 @@ import ac.mdiq.podcini.storage.utils.ChapterUtils
|
|||
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
||||
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.ui.utils.NotificationUtils
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.EventFlow
|
||||
import ac.mdiq.podcini.util.FlowEvent
|
||||
import ac.mdiq.podcini.util.FlowEvent.PlayEvent.Action
|
||||
|
@ -229,9 +227,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
prevPosition = curPosition
|
||||
}
|
||||
}
|
||||
override fun requestWidgetState(): WidgetState {
|
||||
return WidgetState(curMedia, status, curPosition, curDuration, curSpeed)
|
||||
}
|
||||
override fun onChapterLoaded(media: Playable?) {
|
||||
sendNotificationBroadcast(NOTIFICATION_TYPE_RELOAD, 0)
|
||||
}
|
||||
|
@ -313,7 +308,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
sendLocalBroadcast(applicationContext, ACTION_PLAYER_STATUS_CHANGED)
|
||||
bluetoothNotifyChange(newInfo, AVRCP_ACTION_PLAYER_STATUS_CHANGED)
|
||||
bluetoothNotifyChange(newInfo, AVRCP_ACTION_META_CHANGED)
|
||||
taskManager.requestWidgetUpdate()
|
||||
}
|
||||
|
||||
override fun onMediaChanged(reloadUI: Boolean) {
|
||||
|
@ -398,7 +392,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
override fun onPlaybackStart(playable: Playable, position: Int) {
|
||||
val delayInterval = positionUpdateInterval(playable.getDuration())
|
||||
Logd(TAG, "onPlaybackStart position: $position delayInterval: $delayInterval")
|
||||
taskManager.startWidgetUpdater(delayInterval)
|
||||
// if (position != Playable.INVALID_TIME) playable.setPosition(position + (delayInterval/2).toInt())
|
||||
if (position != Playable.INVALID_TIME) playable.setPosition(position)
|
||||
else skipIntro(playable)
|
||||
|
@ -410,7 +403,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
Logd(TAG, "onPlaybackPause $position")
|
||||
taskManager.cancelPositionSaver()
|
||||
persistCurrentPosition(position == Playable.INVALID_TIME || playable == null, playable, position)
|
||||
taskManager.cancelWidgetUpdater()
|
||||
if (playable != null) {
|
||||
if (playable is EpisodeMedia) SynchronizationQueueSink.enqueueEpisodePlayedIfSyncActive(applicationContext, playable, false)
|
||||
playable.onPlaybackPause(applicationContext)
|
||||
|
@ -1303,7 +1295,7 @@ class PlaybackService : MediaLibraryService() {
|
|||
|
||||
/**
|
||||
* Manages the background tasks of PlaybackSerivce, i.e.
|
||||
* the sleep timer, the position saver, the widget updater and the queue loader.
|
||||
* the sleep timer, the position saver, and the queue loader.
|
||||
*
|
||||
* The PlaybackServiceTaskManager(PSTM) uses a callback object (PSTMCallback)
|
||||
* to notify the PlaybackService about updates from the running tasks.
|
||||
|
@ -1316,7 +1308,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
|
||||
private var positionSaverFuture: ScheduledFuture<*>? = null
|
||||
private var widgetUpdaterFuture: ScheduledFuture<*>? = null
|
||||
private var sleepTimerFuture: ScheduledFuture<*>? = null
|
||||
private var sleepTimer: SleepTimer? = null
|
||||
|
||||
|
@ -1331,13 +1322,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
val sleepTimerTimeLeft: Long
|
||||
get() = if (isSleepTimerActive) sleepTimer!!.getWaitingTime() else 0
|
||||
|
||||
/**
|
||||
* Returns true if the widget updater is currently running.
|
||||
*/
|
||||
@get:Synchronized
|
||||
val isWidgetUpdaterActive: Boolean
|
||||
get() = widgetUpdaterFuture != null && !widgetUpdaterFuture!!.isCancelled && !widgetUpdaterFuture!!.isDone
|
||||
|
||||
@get:Synchronized
|
||||
val isPositionSaverActive: Boolean
|
||||
get() = positionSaverFuture != null && !positionSaverFuture!!.isCancelled && !positionSaverFuture!!.isDone
|
||||
|
@ -1360,29 +1344,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun startWidgetUpdater(delayInterval: Long) {
|
||||
if (!isWidgetUpdaterActive && !schedExecutor.isShutdown) {
|
||||
var widgetUpdater = Runnable { this.requestWidgetUpdate() }
|
||||
widgetUpdater = useMainThreadIfNecessary(widgetUpdater)
|
||||
// val delayInterval = positionUpdateInterval(duration)
|
||||
// widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(
|
||||
// widgetUpdater, WIDGET_UPDATER_NOTIFICATION_INTERVAL.toLong(), WIDGET_UPDATER_NOTIFICATION_INTERVAL.toLong(), TimeUnit.MILLISECONDS)
|
||||
widgetUpdaterFuture = schedExecutor.scheduleWithFixedDelay(widgetUpdater, delayInterval, delayInterval, TimeUnit.MILLISECONDS)
|
||||
Logd(TAG, "Started WidgetUpdater")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information about the widget state in the calling thread and then displays it in a background thread.
|
||||
*/
|
||||
@Synchronized
|
||||
fun requestWidgetUpdate() {
|
||||
val state = callback.requestWidgetState()
|
||||
if (!schedExecutor.isShutdown) schedExecutor.execute { WidgetUpdater.updateWidget(context, state) }
|
||||
else Logd(TAG, "Call to requestWidgetUpdate was ignored.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new sleep timer with the given waiting time. If another sleep timer is already active, it will be
|
||||
* cancelled first.
|
||||
|
@ -1415,17 +1376,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the widget updater. If the widget updater is not running, nothing will happen.
|
||||
*/
|
||||
@Synchronized
|
||||
fun cancelWidgetUpdater() {
|
||||
if (isWidgetUpdaterActive) {
|
||||
widgetUpdaterFuture!!.cancel(false)
|
||||
Logd(TAG, "Cancelled WidgetUpdater")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
|
||||
* it will be cancelled first.
|
||||
|
@ -1452,7 +1402,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
@Synchronized
|
||||
fun cancelAllTasks() {
|
||||
cancelPositionSaver()
|
||||
cancelWidgetUpdater()
|
||||
disableSleepTimer()
|
||||
// chapterLoaderFuture?.dispose()
|
||||
// chapterLoaderFuture = null
|
||||
|
@ -1539,7 +1488,6 @@ class PlaybackService : MediaLibraryService() {
|
|||
|
||||
interface PSTMCallback {
|
||||
fun positionSaverTick()
|
||||
fun requestWidgetState(): WidgetState
|
||||
fun onChapterLoaded(media: Playable?)
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ class ExportWorker private constructor(private val exportWriter: ExportWriter, p
|
|||
writer = OutputStreamWriter(FileOutputStream(output), Charset.forName("UTF-8"))
|
||||
val feeds_ = feeds ?: getFeedList()
|
||||
Logd(TAG, "feeds_: ${feeds_.size}")
|
||||
exportWriter.writeDocument(feeds_, writer, context)
|
||||
exportWriter.writeDocument(feeds_, writer!!, context)
|
||||
output // return the output file
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Error during file export", e)
|
||||
|
@ -73,7 +73,7 @@ class DocumentFileExportWorker(private val exportWriter: ExportWriter, private v
|
|||
writer = OutputStreamWriter(outputStream, Charset.forName("UTF-8"))
|
||||
val feeds_ = feeds ?: getFeedList()
|
||||
Logd("DocumentFileExportWorker", "feeds_: ${feeds_.size}")
|
||||
exportWriter.writeDocument(feeds_, writer, context)
|
||||
exportWriter.writeDocument(feeds_, writer!!, context)
|
||||
output
|
||||
} catch (e: IOException) { throw e
|
||||
} finally {
|
||||
|
|
|
@ -7,7 +7,7 @@ import java.io.Writer
|
|||
|
||||
interface ExportWriter {
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
fun writeDocument(feeds: List<Feed>, writer: Writer?, context: Context)
|
||||
fun writeDocument(feeds: List<Feed>, writer: Writer, context: Context)
|
||||
|
||||
fun fileExtension(): String?
|
||||
}
|
|
@ -64,7 +64,7 @@ class OpmlTransporter {
|
|||
* Takes a list of feeds and a writer and writes those into an OPML document.
|
||||
*/
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
override fun writeDocument(feeds: List<Feed>, writer: Writer?, context: Context) {
|
||||
override fun writeDocument(feeds: List<Feed>, writer: Writer, context: Context) {
|
||||
val xs = Xml.newSerializer()
|
||||
xs.setFeature(OpmlSymbols.XML_FEATURE_INDENT_OUTPUT, true)
|
||||
xs.setOutput(writer)
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
package ac.mdiq.podcini.receiver
|
||||
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class PlayerWidget : AppWidgetProvider() {
|
||||
override fun onEnabled(context: Context) {
|
||||
super.onEnabled(context)
|
||||
getSharedPrefs(context)
|
||||
Logd(TAG, "Widget enabled")
|
||||
setEnabled(true)
|
||||
WidgetUpdaterWorker.enqueueWork(context)
|
||||
scheduleWorkaround(context)
|
||||
}
|
||||
|
||||
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
|
||||
Logd(TAG, "onUpdate() called with: context = [$context], appWidgetManager = [$appWidgetManager], appWidgetIds = [${appWidgetIds.contentToString()}]")
|
||||
getSharedPrefs(context)
|
||||
WidgetUpdaterWorker.enqueueWork(context)
|
||||
if (!prefs!!.getBoolean(Prefs.WorkaroundEnabled.name, false)) {
|
||||
scheduleWorkaround(context)
|
||||
prefs!!.edit().putBoolean(Prefs.WorkaroundEnabled.name, true).apply()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
super.onDisabled(context)
|
||||
Logd(TAG, "Widget disabled")
|
||||
setEnabled(false)
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context, appWidgetIds: IntArray) {
|
||||
Logd(TAG, "OnDeleted")
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
prefs!!.edit().remove(Prefs.widget_color.name + appWidgetId).apply()
|
||||
prefs!!.edit().remove(Prefs.widget_playback_speed.name + appWidgetId).apply()
|
||||
prefs!!.edit().remove(Prefs.widget_rewind.name + appWidgetId).apply()
|
||||
prefs!!.edit().remove(Prefs.widget_fast_forward.name + appWidgetId).apply()
|
||||
prefs!!.edit().remove(Prefs.widget_skip.name + appWidgetId).apply()
|
||||
}
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
val widgetIds = manager.getAppWidgetIds(ComponentName(context, PlayerWidget::class.java))
|
||||
if (widgetIds.isEmpty()) {
|
||||
prefs!!.edit().putBoolean(Prefs.WorkaroundEnabled.name, false).apply()
|
||||
WorkManager.getInstance(context).cancelUniqueWork(Prefs.WidgetUpdaterWorkaround.name)
|
||||
}
|
||||
super.onDeleted(context, appWidgetIds)
|
||||
}
|
||||
|
||||
private fun setEnabled(enabled: Boolean) {
|
||||
prefs!!.edit().putBoolean(Prefs.WidgetEnabled.name, enabled).apply()
|
||||
}
|
||||
|
||||
enum class Prefs {
|
||||
widget_color,
|
||||
widget_playback_speed,
|
||||
widget_skip,
|
||||
widget_fast_forward,
|
||||
widget_rewind,
|
||||
WidgetUpdaterWorkaround,
|
||||
WorkaroundEnabled,
|
||||
WidgetEnabled
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = PlayerWidget::class.simpleName ?: "Anonymous"
|
||||
private const val PREFS_NAME: String = "PlayerWidgetPrefs"
|
||||
const val DEFAULT_COLOR: Int = -0xd9d3cf
|
||||
var prefs: SharedPreferences? = null
|
||||
|
||||
fun getSharedPrefs(context: Context) {
|
||||
if (prefs == null) prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
private fun scheduleWorkaround(context: Context) {
|
||||
// Enqueueing work enables a BOOT_COMPLETED receiver, which in turn makes Android refresh widgets.
|
||||
// This creates an endless loop with a flickering widget.
|
||||
// Workaround: When there is a widget, schedule a dummy task in the far future, so that the receiver stays.
|
||||
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(WidgetUpdaterWorker::class.java)
|
||||
.setInitialDelay((100 * 356).toLong(), TimeUnit.DAYS)
|
||||
.build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(Prefs.WidgetUpdaterWorkaround.name, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isEnabled(): Boolean {
|
||||
return prefs!!.getBoolean(Prefs.WidgetEnabled.name, false)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,6 @@ import ac.mdiq.podcini.preferences.UserPreferences.backButtonOpensDrawer
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.defaultPage
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.hiddenDrawerItems
|
||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createIntent
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget
|
||||
import ac.mdiq.podcini.storage.database.Feeds.buildTags
|
||||
import ac.mdiq.podcini.storage.database.Feeds.monitorFeeds
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
|
||||
|
@ -187,7 +186,6 @@ class MainActivity : CastEnabledActivity() {
|
|||
SwipeActions.getSharedPrefs(this@MainActivity)
|
||||
buildTags()
|
||||
monitorFeeds()
|
||||
PlayerWidget.getSharedPrefs(this@MainActivity)
|
||||
}
|
||||
|
||||
if (savedInstanceState != null) ensureGeneratedViewIdGreaterThan(savedInstanceState.getInt(Extras.generated_view_id.name, 0))
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.activity
|
||||
|
||||
import ac.mdiq.podcini.preferences.ThemeSwitcher.getTranslucentTheme
|
||||
import ac.mdiq.podcini.ui.compose.PlaybackSpeedFullDialog
|
||||
import android.os.Bundle
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
|
||||
// This is for widget
|
||||
class PlaybackSpeedDialogActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(getTranslucentTheme(this))
|
||||
super.onCreate(savedInstanceState)
|
||||
val composeView = ComposeView(this).apply {
|
||||
setContent {
|
||||
var showSpeedDialog by remember { mutableStateOf(true) }
|
||||
if (showSpeedDialog) PlaybackSpeedFullDialog(settingCode = booleanArrayOf(true, true, true), indexDefault = 0, maxSpeed = 3f,
|
||||
onDismiss = {
|
||||
showSpeedDialog = false
|
||||
(parent as? ViewGroup)?.removeView(this)
|
||||
finish()
|
||||
})
|
||||
}
|
||||
}
|
||||
(window.decorView as? ViewGroup)?.addView(composeView)
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,144 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.activity
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.ActivityWidgetConfigBinding
|
||||
import ac.mdiq.podcini.databinding.PlayerWidgetBinding
|
||||
import ac.mdiq.podcini.preferences.ThemeSwitcher.getTheme
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.CheckBox
|
||||
import android.widget.SeekBar
|
||||
import android.widget.SeekBar.OnSeekBarChangeListener
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class WidgetConfigActivity : AppCompatActivity() {
|
||||
|
||||
private var _binding: ActivityWidgetConfigBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
private var _wpBinding: PlayerWidgetBinding? = null
|
||||
private val wpBinding get() = _wpBinding!!
|
||||
|
||||
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
|
||||
private lateinit var widgetPreview: View
|
||||
private lateinit var opacitySeekBar: SeekBar
|
||||
private lateinit var opacityTextView: TextView
|
||||
private lateinit var ckPlaybackSpeed: CheckBox
|
||||
private lateinit var ckRewind: CheckBox
|
||||
private lateinit var ckFastForward: CheckBox
|
||||
private lateinit var ckSkip: CheckBox
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
setTheme(getTheme(this))
|
||||
super.onCreate(savedInstanceState)
|
||||
_binding = ActivityWidgetConfigBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
val configIntent = intent
|
||||
val extras = configIntent.extras
|
||||
if (extras != null) appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
|
||||
|
||||
val resultValue = Intent()
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
setResult(RESULT_CANCELED, resultValue)
|
||||
if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) finish()
|
||||
|
||||
opacityTextView = binding.widgetOpacityTextView
|
||||
opacitySeekBar = binding.widgetOpacitySeekBar
|
||||
widgetPreview = binding.widgetConfigPreview.playerWidget
|
||||
_wpBinding = PlayerWidgetBinding.bind(widgetPreview)
|
||||
|
||||
binding.butConfirm.setOnClickListener{ confirmCreateWidget() }
|
||||
opacitySeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
|
||||
opacityTextView.text = seekBar.progress.toString() + "%"
|
||||
val color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
|
||||
widgetPreview.setBackgroundColor(color)
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
|
||||
wpBinding.txtNoPlaying.visibility = View.GONE
|
||||
val title = wpBinding.txtvTitle
|
||||
title.visibility = View.VISIBLE
|
||||
title.setText(R.string.app_name)
|
||||
val progress = wpBinding.txtvProgress
|
||||
progress.visibility = View.VISIBLE
|
||||
progress.setText(R.string.position_default_label)
|
||||
|
||||
ckPlaybackSpeed = binding.ckPlaybackSpeed
|
||||
ckPlaybackSpeed.setOnClickListener { displayPreviewPanel() }
|
||||
ckRewind = binding.ckRewind
|
||||
ckRewind.setOnClickListener { displayPreviewPanel() }
|
||||
ckFastForward = binding.ckFastForward
|
||||
ckFastForward.setOnClickListener { displayPreviewPanel() }
|
||||
ckSkip = binding.ckSkip
|
||||
ckSkip.setOnClickListener { displayPreviewPanel() }
|
||||
|
||||
setInitialState()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
_binding = null
|
||||
_wpBinding = null
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun setInitialState() {
|
||||
PlayerWidget.getSharedPrefs(this)
|
||||
// val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
|
||||
ckPlaybackSpeed.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_playback_speed.name + appWidgetId, true)
|
||||
ckRewind.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_rewind.name + appWidgetId, true)
|
||||
ckFastForward.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_fast_forward.name + appWidgetId, true)
|
||||
ckSkip.isChecked = prefs!!.getBoolean(PlayerWidget.Prefs.widget_skip.name + appWidgetId, true)
|
||||
|
||||
val color = prefs!!.getInt(PlayerWidget.Prefs.widget_color.name + appWidgetId, PlayerWidget.DEFAULT_COLOR)
|
||||
val opacity = Color.alpha(color) * 100 / 0xFF
|
||||
opacitySeekBar.setProgress(opacity, false)
|
||||
displayPreviewPanel()
|
||||
}
|
||||
|
||||
private fun displayPreviewPanel() {
|
||||
val showExtendedPreview = ckPlaybackSpeed.isChecked || ckRewind.isChecked || ckFastForward.isChecked || ckSkip.isChecked
|
||||
wpBinding.extendedButtonsContainer.visibility = if (showExtendedPreview) View.VISIBLE else View.GONE
|
||||
wpBinding.butPlay.visibility = if (showExtendedPreview) View.GONE else View.VISIBLE
|
||||
wpBinding.butPlaybackSpeed.visibility = if (ckPlaybackSpeed.isChecked) View.VISIBLE else View.GONE
|
||||
wpBinding.butFastForward.visibility = if (ckFastForward.isChecked) View.VISIBLE else View.GONE
|
||||
wpBinding.butSkip.visibility = if (ckSkip.isChecked) View.VISIBLE else View.GONE
|
||||
wpBinding.butRew.visibility = if (ckRewind.isChecked) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun confirmCreateWidget() {
|
||||
val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
|
||||
|
||||
Logd("WidgetConfigActivity", "confirmCreateWidget appWidgetId $appWidgetId")
|
||||
// val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
|
||||
val editor = prefs!!.edit()
|
||||
editor.putInt(PlayerWidget.Prefs.widget_color.name + appWidgetId, backgroundColor)
|
||||
editor.putBoolean(PlayerWidget.Prefs.widget_playback_speed.name + appWidgetId, ckPlaybackSpeed.isChecked)
|
||||
editor.putBoolean(PlayerWidget.Prefs.widget_skip.name + appWidgetId, ckSkip.isChecked)
|
||||
editor.putBoolean(PlayerWidget.Prefs.widget_rewind.name + appWidgetId, ckRewind.isChecked)
|
||||
editor.putBoolean(PlayerWidget.Prefs.widget_fast_forward.name + appWidgetId, ckFastForward.isChecked)
|
||||
editor.apply()
|
||||
|
||||
val resultValue = Intent()
|
||||
resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
|
||||
setResult(RESULT_OK, resultValue)
|
||||
finish()
|
||||
WidgetUpdaterWorker.enqueueWork(this)
|
||||
}
|
||||
|
||||
private fun getColorWithAlpha(color: Int, opacity: Int): Int {
|
||||
return (0xFF * (0.01 * opacity)).roundToInt() * 0x1000000 + (color and 0xffffff)
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package ac.mdiq.podcini.ui.compose
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.preferences.UserPreferences
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.appPrefs
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
|
@ -12,11 +10,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
|
@ -195,16 +194,28 @@ fun SimpleSwitchDialog(title: String, text: String, onDismissRequest: ()->Unit,
|
|||
}
|
||||
|
||||
@Composable
|
||||
fun TitleSummaryColumn(titleRes: Int, summaryRes: Int, callback: ()-> Unit) {
|
||||
fun IconTitleSummaryActionRow(vecRes: Int, titleRes: Int, summaryRes: Int, callback: ()-> Unit) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { callback() })) {
|
||||
Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(summaryRes), color = textColor)
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) {
|
||||
Icon(imageVector = ImageVector.vectorResource(vecRes), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp))
|
||||
Column(modifier = Modifier.weight(1f).clickable(onClick = { callback() })) {
|
||||
Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
Text(stringResource(summaryRes), color = textColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitleSummarySwitchRow(titleRes: Int, summaryRes: Int, prefName: String) {
|
||||
fun TitleSummaryActionColumn(titleRes: Int, summaryRes: Int, callback: ()-> Unit) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { callback() })) {
|
||||
Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
|
||||
if (summaryRes != 0) Text(stringResource(summaryRes), color = textColor)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TitleSummarySwitchPrefRow(titleRes: Int, summaryRes: Int, prefName: String) {
|
||||
val textColor = MaterialTheme.colorScheme.onSurface
|
||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) {
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
|
@ -216,4 +227,5 @@ fun TitleSummarySwitchRow(titleRes: Int, summaryRes: Int, prefName: String) {
|
|||
isChecked = it
|
||||
appPrefs.edit().putBoolean(prefName, it).apply() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -223,9 +223,8 @@ class AudioPlayerFragment : Fragment() {
|
|||
if (playbackService == null) PlaybackServiceStarter(requireContext(), curMedia!!).start()
|
||||
}
|
||||
val imgLoc_ = remember(currentMedia) { imgLoc }
|
||||
AsyncImage(model = ImageRequest.Builder(context).data(imgLoc_)
|
||||
AsyncImage(contentDescription = "imgvCover", model = ImageRequest.Builder(context).data(imgLoc_)
|
||||
.memoryCachePolicy(CachePolicy.ENABLED).placeholder(R.mipmap.ic_launcher).error(R.mipmap.ic_launcher).build(),
|
||||
contentDescription = "imgvCover",
|
||||
modifier = Modifier.width(65.dp).height(65.dp).padding(start = 5.dp)
|
||||
.clickable(onClick = {
|
||||
Logd(TAG, "playerUiFragment icon was clicked")
|
||||
|
@ -249,10 +248,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
SpeedometerWithArc(speed = curPlaybackSpeed*100, maxSpeed = 300f, trackColor = textColor,
|
||||
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
|
||||
showSpeedDialog = true
|
||||
// VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
|
||||
}))
|
||||
modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = { showSpeedDialog = true }))
|
||||
// Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, contentDescription = "speed",
|
||||
// modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = {
|
||||
// VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null)
|
||||
|
@ -264,8 +260,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
var showSkipDialog by remember { mutableStateOf(false) }
|
||||
var rewindSecs by remember { mutableStateOf(NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())) }
|
||||
if (showSkipDialog) SkipDialog(SkipDirection.SKIP_REWIND, onDismissRequest = { showSkipDialog = false }) { rewindSecs = it.toString() }
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_rewind), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_rewind), tint = textColor, contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(
|
||||
onClick = { playbackService?.mPlayer?.seekDelta(-UserPreferences.rewindSecs * 1000) },
|
||||
onLongClick = { showSkipDialog = true }))
|
||||
|
@ -273,31 +268,29 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Icon(imageVector = ImageVector.vectorResource(playButRes), tint = textColor, contentDescription = "play",
|
||||
modifier = Modifier.width(64.dp).height(64.dp).combinedClickable(onClick = {
|
||||
if (controller == null) return@combinedClickable
|
||||
if (curMedia != null) {
|
||||
val media = curMedia!!
|
||||
setIsShowPlay(!isShowPlay)
|
||||
if (media.getMediaType() == MediaType.VIDEO && status != PlayerStatus.PLAYING &&
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
|
||||
playPause()
|
||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
|
||||
} else playPause()
|
||||
}
|
||||
}, onLongClick = {
|
||||
// if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
if (status == PlayerStatus.PLAYING) {
|
||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
||||
if (fallbackSpeed > 0.1f) toggleFallbackSpeed(fallbackSpeed)
|
||||
}
|
||||
}))
|
||||
modifier = Modifier.width(64.dp).height(64.dp).combinedClickable(
|
||||
onClick = {
|
||||
if (controller == null) return@combinedClickable
|
||||
if (curMedia != null) {
|
||||
val media = curMedia!!
|
||||
setIsShowPlay(!isShowPlay)
|
||||
if (media.getMediaType() == MediaType.VIDEO && status != PlayerStatus.PLAYING &&
|
||||
(media is EpisodeMedia && media.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY)) {
|
||||
playPause()
|
||||
requireContext().startActivity(getPlayerActivityIntent(requireContext(), curMedia!!.getMediaType()))
|
||||
} else playPause()
|
||||
} },
|
||||
onLongClick = {
|
||||
if (status == PlayerStatus.PLAYING) {
|
||||
val fallbackSpeed = UserPreferences.fallbackSpeed
|
||||
if (fallbackSpeed > 0.1f) toggleFallbackSpeed(fallbackSpeed)
|
||||
} }))
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
var showSkipDialog by remember { mutableStateOf(false) }
|
||||
var fastForwardSecs by remember { mutableStateOf(NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())) }
|
||||
if (showSkipDialog) SkipDialog(SkipDirection.SKIP_FORWARD, onDismissRequest = {showSkipDialog = false }) { fastForwardSecs = it.toString()}
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_forward), tint = textColor,
|
||||
contentDescription = "forward",
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_fast_forward), tint = textColor, contentDescription = "forward",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(
|
||||
onClick = { playbackService?.mPlayer?.seekDelta(UserPreferences.fastForwardSecs * 1000) },
|
||||
onLongClick = { showSkipDialog = true }))
|
||||
|
@ -313,17 +306,14 @@ class AudioPlayerFragment : Fragment() {
|
|||
} else playbackService?.mPlayer?.setPlaybackParams(playbackService!!.normalSpeed, isSkipSilence)
|
||||
playbackService!!.isSpeedForward = !playbackService!!.isSpeedForward
|
||||
}
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_skip_48dp), tint = textColor,
|
||||
contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(onClick = {
|
||||
// if (controller != null && status == PlayerStatus.PLAYING) {
|
||||
if (status == PlayerStatus.PLAYING) {
|
||||
val speedForward = UserPreferences.speedforwardSpeed
|
||||
if (speedForward > 0.1f) speedForward(speedForward)
|
||||
}
|
||||
}, onLongClick = {
|
||||
activity?.sendBroadcast(MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||
}))
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_skip_48dp), tint = textColor, contentDescription = "rewind",
|
||||
modifier = Modifier.width(43.dp).height(43.dp).combinedClickable(
|
||||
onClick = {
|
||||
if (status == PlayerStatus.PLAYING) {
|
||||
val speedForward = UserPreferences.speedforwardSpeed
|
||||
if (speedForward > 0.1f) speedForward(speedForward)
|
||||
} },
|
||||
onLongClick = { activity?.sendBroadcast(MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT)) }))
|
||||
if (UserPreferences.speedforwardSpeed > 0.1f) Text(NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed), color = textColor, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
Spacer(Modifier.weight(0.1f))
|
||||
|
@ -382,9 +372,6 @@ class AudioPlayerFragment : Fragment() {
|
|||
Logd(TAG, "row clicked: $item $selectedOption")
|
||||
if (item != selectedOption) {
|
||||
onOptionSelected(item)
|
||||
// currentItem = upsertBlk(currentItem!!) {
|
||||
// it.media?.volumeAdaptionSetting = item
|
||||
// }
|
||||
if (currentMedia is EpisodeMedia) {
|
||||
(currentMedia as? EpisodeMedia)?.volumeAdaptionSetting = item
|
||||
currentMedia = currentItem!!.media
|
||||
|
@ -454,20 +441,14 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
})
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_volume_adaption), tint = textColor, contentDescription = "Volume adaptation", modifier = Modifier.clickable {
|
||||
if (currentItem != null) {
|
||||
showVolumeDialog = true
|
||||
}
|
||||
if (currentItem != null) showVolumeDialog = true
|
||||
})
|
||||
Icon(imageVector = ImageVector.vectorResource(R.drawable.baseline_offline_share_24), tint = textColor, contentDescription = "Share Note", modifier = Modifier.clickable {
|
||||
val notes = if (showHomeText) readerhtml else feedItem?.description
|
||||
if (!notes.isNullOrEmpty()) {
|
||||
val shareText = HtmlCompat.fromHtml(notes, HtmlCompat.FROM_HTML_MODE_COMPACT).toString()
|
||||
val context = requireContext()
|
||||
val intent = ShareCompat.IntentBuilder(context)
|
||||
.setType("text/plain")
|
||||
.setText(shareText)
|
||||
.setChooserTitle(R.string.share_notes_label)
|
||||
.createChooserIntent()
|
||||
val intent = ShareCompat.IntentBuilder(context).setType("text/plain").setText(shareText).setChooserTitle(R.string.share_notes_label).createChooserIntent()
|
||||
context.startActivity(intent)
|
||||
}
|
||||
})
|
||||
|
@ -479,9 +460,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
@Composable
|
||||
fun DetailUI(modifier: Modifier) {
|
||||
var showChooseRatingDialog by remember { mutableStateOf(false) }
|
||||
if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) {
|
||||
showChooseRatingDialog = false
|
||||
}
|
||||
if (showChooseRatingDialog) ChooseRatingDialog(listOf(currentItem!!)) { showChooseRatingDialog = false }
|
||||
var showChaptersDialog by remember { mutableStateOf(false) }
|
||||
if (showChaptersDialog) ChaptersDialog(media = currentMedia!!, onDismissRequest = {showChaptersDialog = false})
|
||||
|
||||
|
@ -491,9 +470,8 @@ class AudioPlayerFragment : Fragment() {
|
|||
fun copyText(text: String): Boolean {
|
||||
val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java)
|
||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText("Podcini", text))
|
||||
if (Build.VERSION.SDK_INT <= 32) {
|
||||
if (Build.VERSION.SDK_INT <= 32)
|
||||
(requireActivity() as MainActivity).showSnackbarAbovePlayer(resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
|
||||
}
|
||||
return true
|
||||
}
|
||||
Text(txtvPodcastTitle, textAlign = TextAlign.Center, color = textColor, style = MaterialTheme.typography.headlineSmall,
|
||||
|
@ -547,10 +525,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
postDelayed({ }, 50)
|
||||
}
|
||||
}
|
||||
}, update = { webView ->
|
||||
// Logd(TAG, "AndroidView update: $cleanedNotes")
|
||||
webView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes?:"No notes", "text/html", "utf-8", "about:blank")
|
||||
})
|
||||
}, update = { webView -> webView.loadDataWithBaseURL("https://127.0.0.1", cleanedNotes?:"No notes", "text/html", "utf-8", "about:blank") })
|
||||
if (displayedChapterIndex >= 0) {
|
||||
Row(modifier = Modifier.padding(start = 20.dp, end = 20.dp),
|
||||
horizontalArrangement = Arrangement.Center, verticalAlignment = Alignment.CenterVertically) {
|
||||
|
@ -567,8 +542,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
AsyncImage(model = imgLocLarge, contentDescription = "imgvCover", placeholder = painterResource(R.mipmap.ic_launcher), error = painterResource(R.mipmap.ic_launcher),
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 32.dp, end = 32.dp, top = 10.dp).clickable(onClick = {
|
||||
}))
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 32.dp, end = 32.dp, top = 10.dp).clickable(onClick = {}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -628,10 +602,8 @@ class AudioPlayerFragment : Fragment() {
|
|||
onPositionUpdate(FlowEvent.PlaybackPositionEvent(media, media.getPosition(), media.getDuration()))
|
||||
if (prevMedia?.getIdentifier() != media.getIdentifier()) imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media)
|
||||
if (isPlayingVideoLocally && (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) {
|
||||
// (activity as MainActivity).bottomSheet.setLocked(true)
|
||||
(activity as MainActivity).bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
|
||||
}
|
||||
// else (activity as MainActivity).bottomSheet.setLocked(false)
|
||||
prevMedia = media
|
||||
}
|
||||
|
||||
|
@ -694,36 +666,18 @@ class AudioPlayerFragment : Fragment() {
|
|||
val article = readability4J.parse()
|
||||
readerhtml = article.contentWithDocumentsCharsetOrUtf8
|
||||
if (!readerhtml.isNullOrEmpty()) {
|
||||
currentItem = upsertBlk(currentItem!!) {
|
||||
it.setTranscriptIfLonger(readerhtml)
|
||||
}
|
||||
currentItem = upsertBlk(currentItem!!) { it.setTranscriptIfLonger(readerhtml) }
|
||||
homeText = currentItem!!.transcript
|
||||
// persistEpisode(currentItem)
|
||||
}
|
||||
}
|
||||
if (!homeText.isNullOrEmpty()) {
|
||||
// val shownotesCleaner = ShownotesCleaner(requireContext())
|
||||
cleanedNotes = shownotesCleaner?.processShownotes(homeText!!, 0)
|
||||
withContext(Dispatchers.Main) {
|
||||
// shownoteView.loadDataWithBaseURL("https://127.0.0.1",
|
||||
// cleanedNotes ?: "No notes",
|
||||
// "text/html",
|
||||
// "UTF-8",
|
||||
// null)
|
||||
}
|
||||
// withContext(Dispatchers.Main) {}
|
||||
} else withContext(Dispatchers.Main) { Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show() }
|
||||
} else {
|
||||
// val shownotesCleaner = ShownotesCleaner(requireContext())
|
||||
cleanedNotes = shownotesCleaner?.processShownotes(currentItem?.description ?: "", currentMedia?.getDuration() ?: 0)
|
||||
if (!cleanedNotes.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
// shownoteView.loadDataWithBaseURL("https://127.0.0.1",
|
||||
// cleanedNotes ?: "No notes",
|
||||
// "text/html",
|
||||
// "UTF-8",
|
||||
// null)
|
||||
}
|
||||
} else withContext(Dispatchers.Main) { Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show() }
|
||||
if (cleanedNotes.isNullOrEmpty())
|
||||
withContext(Dispatchers.Main) { Toast.makeText(context, R.string.web_content_not_available, Toast.LENGTH_LONG).show() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -782,8 +736,6 @@ class AudioPlayerFragment : Fragment() {
|
|||
Logd(TAG, "Saving preferences")
|
||||
val editor = prefs.edit() ?: return
|
||||
if (curMedia != null) {
|
||||
// Logd(TAG, "Saving scroll position: " + binding.itemDescriptionFragment.scrollY)
|
||||
// editor.putInt(PREF_SCROLL_Y, binding.itemDescriptionFragment.scrollY)
|
||||
editor.putString(PREF_PLAYABLE_ID, curMedia!!.getIdentifier().toString())
|
||||
} else {
|
||||
Logd(TAG, "savePreferences was called while media or webview was null")
|
||||
|
@ -799,7 +751,6 @@ class AudioPlayerFragment : Fragment() {
|
|||
// if (isCollapsed) {
|
||||
isCollapsed = false
|
||||
if (shownotesCleaner == null) shownotesCleaner = ShownotesCleaner(requireContext())
|
||||
// showPlayer1 = false
|
||||
// if (currentMedia != null) updateUi(currentMedia!!)
|
||||
setIsShowPlay(isShowPlay)
|
||||
updateDetails()
|
||||
|
@ -809,7 +760,6 @@ class AudioPlayerFragment : Fragment() {
|
|||
fun onCollaped() {
|
||||
Logd(TAG, "onCollaped()")
|
||||
isCollapsed = true
|
||||
// showPlayer1 = true
|
||||
// if (currentMedia != null) updateUi(currentMedia!!)
|
||||
setIsShowPlay(isShowPlay)
|
||||
}
|
||||
|
@ -882,7 +832,6 @@ class AudioPlayerFragment : Fragment() {
|
|||
private fun createHandler(): ServiceStatusHandler {
|
||||
return object : ServiceStatusHandler(requireActivity()) {
|
||||
override fun updatePlayButton(showPlay: Boolean) {
|
||||
// isShowPlay = showPlay
|
||||
setIsShowPlay(showPlay)
|
||||
}
|
||||
override fun loadMediaInfo() {
|
||||
|
@ -890,18 +839,12 @@ class AudioPlayerFragment : Fragment() {
|
|||
// if (!isCollapsed) updateDetails()
|
||||
}
|
||||
override fun onPlaybackEnd() {
|
||||
// isShowPlay = true
|
||||
setIsShowPlay(true)
|
||||
(activity as MainActivity).setPlayerVisible(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// super.onCreate(savedInstanceState)
|
||||
// retainInstance = true
|
||||
// }
|
||||
|
||||
override fun onResume() {
|
||||
Logd(TAG, "onResume() isCollapsed: $isCollapsed")
|
||||
super.onResume()
|
||||
|
@ -1016,10 +959,7 @@ class AudioPlayerFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun onRatingEvent(event: FlowEvent.RatingEvent) {
|
||||
if (curEpisode?.id == event.episode.id) {
|
||||
rating = event.rating
|
||||
// EpisodeMenuHandler.onPrepareMenu(toolbar.menu, event.episode)
|
||||
}
|
||||
if (curEpisode?.id == event.episode.id) rating = event.rating
|
||||
}
|
||||
|
||||
// fun scrollToTop() {
|
||||
|
|
|
@ -1,232 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.widget
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
|
||||
import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createPendingIntent
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.isEnabled
|
||||
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
|
||||
import ac.mdiq.podcini.storage.model.MediaType
|
||||
import ac.mdiq.podcini.storage.model.Playable
|
||||
import ac.mdiq.podcini.storage.utils.DurationConverter.getDurationStringLong
|
||||
import ac.mdiq.podcini.storage.utils.ImageResourceUtils.getFallbackImageLocation
|
||||
import ac.mdiq.podcini.storage.utils.TimeSpeedConverter
|
||||
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
|
||||
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.util.Log
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import coil.imageLoader
|
||||
import coil.request.ErrorResult
|
||||
import coil.request.ImageRequest
|
||||
import coil.request.SuccessResult
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.math.max
|
||||
|
||||
object WidgetUpdater {
|
||||
private val TAG: String = WidgetUpdater::class.simpleName ?: "Anonymous"
|
||||
|
||||
/**
|
||||
* Update the widgets with the given parameters. Must be called in a background thread.
|
||||
*/
|
||||
fun updateWidget(context: Context, widgetState: WidgetState?) {
|
||||
if (!isEnabled() || widgetState == null) return
|
||||
Logd(TAG, "in updateWidget")
|
||||
|
||||
val startMediaPlayer =
|
||||
if (widgetState.media != null && widgetState.media.getMediaType() === MediaType.VIDEO) VideoPlayerActivityStarter(context).pendingIntent
|
||||
else MainActivityStarter(context).withOpenPlayer().pendingIntent
|
||||
|
||||
val startPlaybackSpeedDialog = PlaybackSpeedActivityStarter(context).pendingIntent
|
||||
val views = RemoteViews(context.packageName, R.layout.player_widget)
|
||||
|
||||
if (widgetState.media != null) {
|
||||
var icon: Bitmap? = null
|
||||
val iconSize = context.resources.getDimensionPixelSize(android.R.dimen.app_icon_size)
|
||||
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer)
|
||||
views.setOnClickPendingIntent(R.id.imgvCover, startMediaPlayer)
|
||||
views.setOnClickPendingIntent(R.id.butPlaybackSpeed, startPlaybackSpeedDialog)
|
||||
|
||||
try {
|
||||
val imgLoc = widgetState.media.getImageLocation()
|
||||
val imgLoc1 = getFallbackImageLocation(widgetState.media)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(imgLoc)
|
||||
.setHeader("User-Agent", "Mozilla/5.0")
|
||||
.placeholder(R.color.light_gray)
|
||||
.listener(object : ImageRequest.Listener {
|
||||
override fun onError(request: ImageRequest, throwable: ErrorResult) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val fallbackImageRequest = ImageRequest.Builder(context)
|
||||
.data(imgLoc1)
|
||||
.setHeader("User-Agent", "Mozilla/5.0")
|
||||
.error(R.mipmap.ic_launcher)
|
||||
.size(iconSize, iconSize)
|
||||
.build()
|
||||
val result = (context.imageLoader.execute(fallbackImageRequest) as SuccessResult).drawable
|
||||
icon = (result as BitmapDrawable).bitmap
|
||||
}
|
||||
}
|
||||
})
|
||||
.size(iconSize, iconSize)
|
||||
.build()
|
||||
withContext(Dispatchers.Main) {
|
||||
val result = (context.imageLoader.execute(request) as SuccessResult).drawable
|
||||
icon = (result as BitmapDrawable).bitmap
|
||||
try {
|
||||
if (icon != null) views.setImageViewBitmap(R.id.imgvCover, icon)
|
||||
else views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
||||
} catch(e: Exception) {
|
||||
Log.e(TAG, e.message?:"")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (tr1: Throwable) {
|
||||
Log.e(TAG, "Error loading the media icon for the widget", tr1)
|
||||
}
|
||||
|
||||
views.setTextViewText(R.id.txtvTitle, widgetState.media.getEpisodeTitle())
|
||||
views.setViewVisibility(R.id.txtvTitle, View.VISIBLE)
|
||||
views.setViewVisibility(R.id.txtNoPlaying, View.GONE)
|
||||
|
||||
val progressString = getProgressString(widgetState.position, widgetState.duration, widgetState.playbackSpeed)
|
||||
if (progressString != null) {
|
||||
views.setViewVisibility(R.id.txtvProgress, View.VISIBLE)
|
||||
views.setTextViewText(R.id.txtvProgress, progressString)
|
||||
}
|
||||
|
||||
if (widgetState.status == PlayerStatus.PLAYING) {
|
||||
views.setImageViewResource(R.id.butPlay, R.drawable.ic_widget_pause)
|
||||
views.setContentDescription(R.id.butPlay, context.getString(R.string.pause_label))
|
||||
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_widget_pause)
|
||||
views.setContentDescription(R.id.butPlayExtended, context.getString(R.string.pause_label))
|
||||
} else {
|
||||
views.setImageViewResource(R.id.butPlay, R.drawable.ic_widget_play)
|
||||
views.setContentDescription(R.id.butPlay, context.getString(R.string.play_label))
|
||||
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_widget_play)
|
||||
views.setContentDescription(R.id.butPlayExtended, context.getString(R.string.play_label))
|
||||
}
|
||||
views.setOnClickPendingIntent(R.id.butPlay, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
|
||||
views.setOnClickPendingIntent(R.id.butPlayExtended, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
|
||||
views.setOnClickPendingIntent(R.id.butRew, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_REWIND))
|
||||
views.setOnClickPendingIntent(R.id.butFastForward, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD))
|
||||
views.setOnClickPendingIntent(R.id.butSkip, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT))
|
||||
} else {
|
||||
// start the app if they click anything
|
||||
views.setOnClickPendingIntent(R.id.layout_left, startMediaPlayer)
|
||||
views.setOnClickPendingIntent(R.id.butPlay, startMediaPlayer)
|
||||
views.setOnClickPendingIntent(R.id.butPlayExtended, createPendingIntent(context, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))
|
||||
views.setViewVisibility(R.id.txtvProgress, View.GONE)
|
||||
views.setViewVisibility(R.id.txtvTitle, View.GONE)
|
||||
views.setViewVisibility(R.id.txtNoPlaying, View.VISIBLE)
|
||||
views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher)
|
||||
views.setImageViewResource(R.id.butPlay, R.drawable.ic_widget_play)
|
||||
views.setImageViewResource(R.id.butPlayExtended, R.drawable.ic_widget_play)
|
||||
}
|
||||
|
||||
val playerWidget = ComponentName(context, PlayerWidget::class.java)
|
||||
val manager = AppWidgetManager.getInstance(context)
|
||||
val widgetIds = manager.getAppWidgetIds(playerWidget)
|
||||
|
||||
for (id in widgetIds) {
|
||||
Logd(TAG, "updating widget $id")
|
||||
val options = manager.getAppWidgetOptions(id)
|
||||
// val prefs = context.getSharedPreferences(PlayerWidget.PREFS_NAME, Context.MODE_PRIVATE)
|
||||
val minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)
|
||||
val columns = getCellsForSize(minWidth)
|
||||
if (columns < 3) views.setViewVisibility(R.id.layout_center, View.INVISIBLE)
|
||||
else views.setViewVisibility(R.id.layout_center, View.VISIBLE)
|
||||
|
||||
val showPlaybackSpeed = prefs!!.getBoolean(PlayerWidget.Prefs.widget_playback_speed.name + id, true)
|
||||
val showRewind = prefs!!.getBoolean(PlayerWidget.Prefs.widget_rewind.name + id, true)
|
||||
val showFastForward = prefs!!.getBoolean(PlayerWidget.Prefs.widget_fast_forward.name + id, true)
|
||||
val showSkip = prefs!!.getBoolean(PlayerWidget.Prefs.widget_skip.name + id, true)
|
||||
|
||||
if (showPlaybackSpeed || showRewind || showSkip || showFastForward) {
|
||||
views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.VISIBLE)
|
||||
views.setInt(R.id.butPlay, "setVisibility", View.GONE)
|
||||
views.setInt(R.id.butPlaybackSpeed, "setVisibility", if (showPlaybackSpeed) View.VISIBLE else View.GONE)
|
||||
views.setInt(R.id.butRew, "setVisibility", if (showRewind) View.VISIBLE else View.GONE)
|
||||
views.setInt(R.id.butFastForward, "setVisibility", if (showFastForward) View.VISIBLE else View.GONE)
|
||||
views.setInt(R.id.butSkip, "setVisibility", if (showSkip) View.VISIBLE else View.GONE)
|
||||
} else {
|
||||
views.setInt(R.id.extendedButtonsContainer, "setVisibility", View.GONE)
|
||||
views.setInt(R.id.butPlay, "setVisibility", View.VISIBLE)
|
||||
}
|
||||
|
||||
val backgroundColor = prefs!!.getInt(PlayerWidget.Prefs.widget_color.name + id, PlayerWidget.DEFAULT_COLOR)
|
||||
views.setInt(R.id.widgetLayout, "setBackgroundColor", backgroundColor)
|
||||
|
||||
manager.updateAppWidget(id, views)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of cells needed for given size of the widget.
|
||||
*
|
||||
* @param size Widget size in dp.
|
||||
* @return Size in number of cells.
|
||||
*/
|
||||
private fun getCellsForSize(size: Int): Int {
|
||||
var n = 2
|
||||
while (70 * n - 30 < size) {
|
||||
++n
|
||||
}
|
||||
return n - 1
|
||||
}
|
||||
|
||||
private fun getProgressString(position: Int, duration: Int, speed: Float): String? {
|
||||
if (position < 0 || duration <= 0) return null
|
||||
|
||||
val converter = TimeSpeedConverter(speed)
|
||||
return if (shouldShowRemainingTime())
|
||||
("${getDurationStringLong(converter.convert(position))} / -${getDurationStringLong(converter.convert(max(0.0, (duration - position).toDouble()).toInt()))}")
|
||||
else (getDurationStringLong(converter.convert(position)) + " / " + getDurationStringLong(converter.convert(duration)))
|
||||
|
||||
}
|
||||
|
||||
class WidgetState(val media: Playable?, val status: PlayerStatus, val position: Int, val duration: Int, val playbackSpeed: Float) {
|
||||
constructor(status: PlayerStatus) : this(null, status, Playable.INVALID_TIME, Playable.INVALID_TIME, 1.0f)
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the playback speed dialog activity of the app with specific arguments.
|
||||
* Does not require a dependency on the actual implementation of the activity.
|
||||
*/
|
||||
class PlaybackSpeedActivityStarter(private val context: Context) {
|
||||
val intent: Intent = Intent(INTENT)
|
||||
|
||||
init {
|
||||
intent.setPackage(context.packageName)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
|
||||
}
|
||||
|
||||
val pendingIntent: PendingIntent
|
||||
get() = PendingIntent.getActivity(context, R.id.pending_intent_playback_speed, intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
fun start() {
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INTENT: String = "ac.mdiq.podcini.intents.PLAYBACK_SPEED"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package ac.mdiq.podcini.ui.widget
|
||||
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
|
||||
import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.getCurrentPlaybackSpeed
|
||||
import ac.mdiq.podcini.playback.base.PlayerStatus
|
||||
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
|
||||
import ac.mdiq.podcini.util.Logd
|
||||
import android.content.Context
|
||||
import androidx.work.*
|
||||
|
||||
class WidgetUpdaterWorker(context: Context, workerParams: WorkerParameters) : Worker(context, workerParams) {
|
||||
|
||||
override fun doWork(): Result {
|
||||
try { updateWidget()
|
||||
} catch (e: Exception) {
|
||||
Logd(TAG, "Failed to update Podcini widget: $e")
|
||||
return Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the current media from the database and updates the widget in a background job.
|
||||
*/
|
||||
private fun updateWidget() {
|
||||
val media = curMedia
|
||||
if (media != null) WidgetUpdater.updateWidget(applicationContext, WidgetState(media, PlayerStatus.STOPPED, media.getPosition(), media.getDuration(), getCurrentPlaybackSpeed(media)))
|
||||
else WidgetUpdater.updateWidget(applicationContext, WidgetState(PlayerStatus.STOPPED))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG: String = WidgetUpdaterWorker::class.simpleName ?: "Anonymous"
|
||||
|
||||
fun enqueueWork(context: Context) {
|
||||
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(WidgetUpdaterWorker::class.java).build()
|
||||
WorkManager.getInstance(context).enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, workRequest)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,125 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/activity_widget_config"
|
||||
tools:context="ac.mdiq.podcini.ui.activity.WidgetConfigActivity">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_config_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@drawable/teaser" />
|
||||
|
||||
<include
|
||||
android:id="@+id/widget_config_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="96dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="16dp"
|
||||
layout="@layout/player_widget" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/widget_opacity"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_opacity_textView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="end"
|
||||
android:text="100%"
|
||||
android:textSize="16sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<SeekBar
|
||||
android:id="@+id/widget_opacity_seekBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:max="100"
|
||||
android:progress="100" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckPlaybackSpeed"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/playback_speed" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckRewind"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/rewind_label" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckFastForward"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/fast_forward_label" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/ckSkip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/skip_episode_label" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/butConfirm"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="@string/widget_create_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,160 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/player_widget"
|
||||
android:padding="@dimen/widget_margin">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/widgetLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#262C31"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="@android:dimen/app_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/play_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="8dp"
|
||||
android:src="@drawable/ic_widget_play" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_left"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:layout_toStartOf="@id/butPlay"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="fill_horizontal"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="@android:dimen/app_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtNoPlaying"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="3"
|
||||
android:text="@string/no_media_playing_label"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/extendedButtonsContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlaybackSpeed"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/playback_speed"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_widget_playback_speed" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butRew"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/rewind_label"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_widget_fast_rewind" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlayExtended"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/play_label"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_widget_play" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butFastForward"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/fast_forward_label"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_widget_fast_forward" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butSkip"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:contentDescription="@string/skip_episode_label"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:scaleType="centerInside"
|
||||
android:src="@drawable/ic_widget_skip" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</FrameLayout>
|
|
@ -1,13 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:initialLayout="@layout/player_widget"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:previewImage="@drawable/ic_widget_preview"
|
||||
android:minHeight="40dp"
|
||||
android:minWidth="100dp"
|
||||
android:minResizeWidth="70dp"
|
||||
android:configure="ac.mdiq.podcini.ui.activity.WidgetConfigActivity"
|
||||
android:widgetFeatures="reconfigurable">
|
||||
</appwidget-provider>
|
|
@ -1,3 +1,9 @@
|
|||
# 6.15.1
|
||||
|
||||
* Consolidated Compose code blocks in PreferenceActivity with function calls
|
||||
* removed support for widget
|
||||
* some minor cleanups
|
||||
|
||||
# 6.15.0
|
||||
|
||||
* added a combo Epiosdes fragment with easy access to various filters
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
Version 6.15.1
|
||||
|
||||
* Consolidated Compose code blocks in PreferenceActivity with function calls
|
||||
* removed support for widget
|
||||
* some minor cleanups
|
Loading…
Reference in New Issue