From f2f4b8dcee94919c9830fb911dad0ee02a4e9964 Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:46:59 +0100 Subject: [PATCH] 6.15.1 commit --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 34 - .../podcini/playback/ServiceStatusHandler.kt | 4 +- .../podcini/playback/base/LocalMediaPlayer.kt | 20 +- .../podcini/playback/base/MediaPlayerBase.kt | 4 +- .../playback/service/PlaybackService.kt | 54 +- .../mdiq/podcini/preferences/ExportWorker.kt | 4 +- .../mdiq/podcini/preferences/ExportWriter.kt | 2 +- .../podcini/preferences/OpmlTransporter.kt | 2 +- .../ac/mdiq/podcini/receiver/PlayerWidget.kt | 99 -- .../mdiq/podcini/ui/activity/MainActivity.kt | 2 - .../activity/PlaybackSpeedDialogActivity.kt | 32 - .../podcini/ui/activity/PreferenceActivity.kt | 854 +++--------------- .../ui/activity/WidgetConfigActivity.kt | 144 --- .../ac/mdiq/podcini/ui/compose/Composables.kt | 30 +- .../ui/fragment/AudioPlayerFragment.kt | 140 +-- .../mdiq/podcini/ui/widget/WidgetUpdater.kt | 232 ----- .../podcini/ui/widget/WidgetUpdaterWorker.kt | 39 - .../res/layout/activity_widget_config.xml | 125 --- app/src/main/res/layout/player_widget.xml | 160 ---- app/src/main/res/xml/player_widget_info.xml | 13 - changelog.md | 6 + .../android/en-US/changelogs/3020309.txt | 5 + 23 files changed, 223 insertions(+), 1786 deletions(-) delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/receiver/PlayerWidget.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PlaybackSpeedDialogActivity.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt delete mode 100644 app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt delete mode 100644 app/src/main/res/layout/activity_widget_config.xml delete mode 100644 app/src/main/res/layout/player_widget.xml delete mode 100644 app/src/main/res/xml/player_widget_info.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/3020309.txt diff --git a/app/build.gradle b/app/build.gradle index 810e4156..5ed916ff 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 = "" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b7d00313..404ecc0b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -92,18 +92,6 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/ServiceStatusHandler.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/ServiceStatusHandler.kt index 2797500e..fcb8512f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/ServiceStatusHandler.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/ServiceStatusHandler.kt @@ -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 -> {} } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt index 9e616cac..8d741f9f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/LocalMediaPlayer.kt @@ -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. diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt index ce68079f..5bab69a3 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/base/MediaPlayerBase.kt @@ -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 /* diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 62c85261..5f7391f0 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -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?) } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWorker.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWorker.kt index ebfe06ce..0267f58d 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWorker.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWorker.kt @@ -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 { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWriter.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWriter.kt index 3c31ed78..5c56eca2 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWriter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/ExportWriter.kt @@ -7,7 +7,7 @@ import java.io.Writer interface ExportWriter { @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - fun writeDocument(feeds: List, writer: Writer?, context: Context) + fun writeDocument(feeds: List, writer: Writer, context: Context) fun fileExtension(): String? } \ No newline at end of file diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt index 34b16aa2..5c6828db 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/OpmlTransporter.kt @@ -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, writer: Writer?, context: Context) { + override fun writeDocument(feeds: List, writer: Writer, context: Context) { val xs = Xml.newSerializer() xs.setFeature(OpmlSymbols.XML_FEATURE_INDENT_OUTPUT, true) xs.setOutput(writer) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/receiver/PlayerWidget.kt b/app/src/main/kotlin/ac/mdiq/podcini/receiver/PlayerWidget.kt deleted file mode 100644 index 1f91ae40..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/receiver/PlayerWidget.kt +++ /dev/null @@ -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) - } - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt index b3840d54..f36de098 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/MainActivity.kt @@ -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)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PlaybackSpeedDialogActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PlaybackSpeedDialogActivity.kt deleted file mode 100644 index d7c688d0..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PlaybackSpeedDialogActivity.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt index 3d664af1..26ea5117 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/PreferenceActivity.kt @@ -9,7 +9,6 @@ import ac.mdiq.podcini.net.download.service.PodciniHttpClient.getHttpClient import ac.mdiq.podcini.net.download.service.PodciniHttpClient.newBuilder import ac.mdiq.podcini.net.download.service.PodciniHttpClient.reinit import ac.mdiq.podcini.net.feed.FeedUpdateManager.restartUpdateAlarm -import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce import ac.mdiq.podcini.net.sync.SyncService import ac.mdiq.podcini.net.sync.SyncService.Companion.isValidGuid import ac.mdiq.podcini.net.sync.SynchronizationCredentials @@ -45,7 +44,6 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl import ac.mdiq.podcini.storage.database.Episodes.getEpisodes import ac.mdiq.podcini.storage.database.Episodes.hasAlmostEnded import ac.mdiq.podcini.storage.database.Feeds.getFeedList -import ac.mdiq.podcini.storage.database.Feeds.updateFeed import ac.mdiq.podcini.storage.database.Queues.EnqueueLocation import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk @@ -53,8 +51,11 @@ import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.utils.FileNameGenerator.generateFileName import ac.mdiq.podcini.ui.actions.SwipeActions.Companion.SwipeActionsDialog import ac.mdiq.podcini.ui.compose.CustomTheme +import ac.mdiq.podcini.ui.compose.IconTitleSummaryActionRow import ac.mdiq.podcini.ui.compose.OpmlImportSelectionDialog import ac.mdiq.podcini.ui.compose.PlaybackSpeedDialog +import ac.mdiq.podcini.ui.compose.TitleSummaryActionColumn +import ac.mdiq.podcini.ui.compose.TitleSummarySwitchPrefRow import ac.mdiq.podcini.ui.fragment.* import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr import ac.mdiq.podcini.util.EventFlow @@ -78,7 +79,6 @@ import android.text.method.HideReturnsTransformationMethod import android.text.method.PasswordTransformationMethod import android.util.Log import android.util.Patterns -import android.util.SparseBooleanArray import android.view.* import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager @@ -146,6 +146,7 @@ import java.util.* import java.util.concurrent.TimeUnit import java.util.regex.Pattern import javax.xml.parsers.DocumentBuilderFactory +import kotlin.Throws import kotlin.math.round /** @@ -252,6 +253,7 @@ class PreferenceActivity : AppCompatActivity() { s.show() } + class MainPreferencesFragment : PreferenceFragmentCompat() { var copyrightNoticeText by mutableStateOf("") @@ -270,6 +272,31 @@ class PreferenceActivity : AppCompatActivity() { packageHash == 1297601420 -> copyrightNoticeText = "This is a development version of Podcini and not meant for daily use" } + @Composable + fun IconTitleSummaryScreenRow(vecRes: Int, titleRes: Int, summaryRes: Int, screen: Screens) { + val textColor = MaterialTheme.colorScheme.onSurface + 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 = { + (activity as PreferenceActivity).openScreen(screen) + })) { + Text(stringResource(titleRes), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) + Text(stringResource(summaryRes), color = textColor) + } + } + } + + @Composable + fun IconTitleActionRow(vecRes: Int, titleRes: Int, callback: ()-> Unit) { + val textColor = MaterialTheme.colorScheme.onSurface + 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) + } + } + } + return ComposeView(requireContext()).apply { setContent { CustomTheme(requireContext()) { @@ -282,59 +309,12 @@ class PreferenceActivity : AppCompatActivity() { Text(copyrightNoticeText, color = textColor) } } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_appearance), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_user_interface) - })) { - Text(stringResource(R.string.user_interface_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.user_interface_sum), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_play_24dp), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_playback) - })) { - Text(stringResource(R.string.playback_pref), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.playback_pref_sum), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_download), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_downloads) - })) { - Text(stringResource(R.string.downloads_pref), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.downloads_pref_sum), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_cloud), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_synchronization) - })) { - Text(stringResource(R.string.synchronization_pref), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.synchronization_sum), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_storage), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_import_export) - })) { - Text(stringResource(R.string.import_export_pref), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.import_export_summary), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_notifications), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_notifications) - })) { - Text(stringResource(R.string.notification_pref_fragment), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } + IconTitleSummaryScreenRow(R.drawable.ic_appearance, R.string.user_interface_label, R.string.user_interface_sum, Screens.preferences_user_interface) + IconTitleSummaryScreenRow(R.drawable.ic_play_24dp, R.string.playback_pref, R.string.playback_pref_sum, Screens.preferences_playback) + IconTitleSummaryScreenRow(R.drawable.ic_download, R.string.downloads_pref, R.string.downloads_pref_sum, Screens.preferences_downloads) + IconTitleSummaryScreenRow(R.drawable.ic_cloud, R.string.synchronization_pref, R.string.synchronization_sum, Screens.preferences_synchronization) + IconTitleSummaryScreenRow(R.drawable.ic_storage, R.string.import_export_pref, R.string.import_export_summary, Screens.preferences_import_export) + IconTitleActionRow(R.drawable.ic_notifications, R.string.notification_pref_fragment) { (activity as PreferenceActivity).openScreen(Screens.preferences_notifications) } Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { Column(modifier = Modifier.weight(1f)) { Text(stringResource(R.string.pref_backup_on_google_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) @@ -352,46 +332,11 @@ class PreferenceActivity : AppCompatActivity() { } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) Text(stringResource(R.string.project_pref), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_questionmark), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini") - })) { - Text(stringResource(R.string.documentation_support), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_chat), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/discussions") - })) { - Text(stringResource(R.string.visit_user_forum), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_contribute), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini") - })) { - Text(stringResource(R.string.pref_contribute), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_bug), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - startActivity(Intent(activity, BugReportActivity::class.java)) - })) { - Text(stringResource(R.string.bug_report_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 10.dp, top = 10.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "", tint = textColor, modifier = Modifier.size(40.dp).padding(end = 15.dp)) - Column(modifier = Modifier.weight(1f).clickable(onClick = { - parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, AboutFragment()).addToBackStack(getString(R.string.about_pref)).commit() - })) { - Text(stringResource(R.string.about_pref), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - } - } + IconTitleActionRow(R.drawable.ic_questionmark, R.string.documentation_support) { openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini") } + IconTitleActionRow(R.drawable.ic_chat, R.string.visit_user_forum) { openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/discussions") } + IconTitleActionRow(R.drawable.ic_contribute, R.string.pref_contribute) { openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini") } + IconTitleActionRow(R.drawable.ic_bug, R.string.bug_report_title) { startActivity(Intent(activity, BugReportActivity::class.java)) } + IconTitleActionRow(R.drawable.ic_info, R.string.about_pref) { parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, AboutFragment()).addToBackStack(getString(R.string.about_pref)).commit() } } } } @@ -422,33 +367,9 @@ class PreferenceActivity : AppCompatActivity() { Text(String.format("%s (%s)", BuildConfig.VERSION_NAME, BuildConfig.COMMIT_HASH), color = textColor) } } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_questionmark), contentDescription = "", tint = textColor) - Column(Modifier.padding(start = 10.dp).clickable(onClick = { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/") - })) { - Text(stringResource(R.string.online_help), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.online_help_sum), color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "", tint = textColor) - Column(Modifier.padding(start = 10.dp).clickable(onClick = { - openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md") - })) { - Text(stringResource(R.string.privacy_policy), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text("Podcini PrivacyPolicy", color = textColor) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(start = 10.dp, top = 5.dp, bottom = 5.dp)) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_info), contentDescription = "", tint = textColor) - Column(Modifier.padding(start = 10.dp).clickable(onClick = { - parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, LicensesFragment()).addToBackStack(getString(R.string.translators)).commit() - })) { - Text(stringResource(R.string.licenses), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.licenses_summary), color = textColor) - } - } + IconTitleSummaryActionRow(R.drawable.ic_questionmark, R.string.online_help, R.string.online_help_sum) { openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/") } + IconTitleSummaryActionRow(R.drawable.ic_info, R.string.privacy_policy, R.string.privacy_policy) { openInBrowser(requireContext(), "https://github.com/XilinJia/Podcini/blob/main/PrivacyPolicy.md") } + IconTitleSummaryActionRow(R.drawable.ic_info, R.string.licenses, R.string.licenses_summary) { parentFragmentManager.beginTransaction().replace(R.id.settingsContainer, LicensesFragment()).addToBackStack(getString(R.string.translators)).commit() } } } } @@ -593,95 +514,21 @@ class PreferenceActivity : AppCompatActivity() { ActivityCompat.recreate(requireActivity()) }) } -// Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { -// drawerPreferencesDialog(requireContext(), null) -// })) { -// Text(stringResource(R.string.pref_nav_drawer_items_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) -// Text(stringResource(R.string.pref_nav_drawer_items_sum), color = textColor) -// } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_episode_cover_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_episode_cover_summary), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefEpisodeCover.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefEpisodeCover.name, it).apply() - }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_show_remain_time_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_show_remain_time_summary), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.showTimeLeft.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.showTimeLeft.name, it).apply() - }) - } + TitleSummarySwitchPrefRow(R.string.pref_episode_cover_title, R.string.pref_episode_cover_summary, UserPreferences.Prefs.prefEpisodeCover.name) + TitleSummarySwitchPrefRow(R.string.pref_show_remain_time_title, R.string.pref_show_remain_time_summary, UserPreferences.Prefs.showTimeLeft.name) Text(stringResource(R.string.subscriptions_label), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_swipe_refresh_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_swipe_refresh_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefSwipeToRefreshAll.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefSwipeToRefreshAll.name, it).apply() - }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_feedGridLayout_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_feedGridLayout_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefFeedGridLayout.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefFeedGridLayout.name, it).apply() - }) - } + TitleSummarySwitchPrefRow(R.string.pref_swipe_refresh_title, R.string.pref_swipe_refresh_sum, UserPreferences.Prefs.prefSwipeToRefreshAll.name) + TitleSummarySwitchPrefRow(R.string.pref_feedGridLayout_title, R.string.pref_feedGridLayout_sum, UserPreferences.Prefs.prefFeedGridLayout.name) Text(stringResource(R.string.external_elements), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) if (Build.VERSION.SDK_INT < 26) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_expandNotify_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_expandNotify_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefExpandNotify.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefExpandNotify.name, it).apply() - }) - } - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_persistNotify_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_persistNotify_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefPersistNotify.name, it).apply() - }) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - showFullNotificationButtonsDialog() - })) { - Text(stringResource(R.string.pref_full_notification_buttons_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_full_notification_buttons_sum), color = textColor) + TitleSummarySwitchPrefRow(R.string.pref_expandNotify_title, R.string.pref_expandNotify_sum, UserPreferences.Prefs.prefExpandNotify.name) } + TitleSummarySwitchPrefRow(R.string.pref_persistNotify_title, R.string.pref_persistNotify_sum, UserPreferences.Prefs.prefPersistNotify.name) + TitleSummaryActionColumn(R.string.pref_full_notification_buttons_title, R.string.pref_full_notification_buttons_sum) { showFullNotificationButtonsDialog() } Text(stringResource(R.string.behavior), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) var showDefaultPageOptions by remember { mutableStateOf(false) } var tempSelectedOption by remember { mutableStateOf(appPrefs.getString(UserPreferences.Prefs.prefDefaultPage.name, DefaultPages.SubscriptionsFragment.name)!!) } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showDefaultPageOptions = true })) { - Text(stringResource(R.string.pref_default_page), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_default_page_sum), color = textColor) - } + TitleSummaryActionColumn(R.string.pref_default_page, R.string.pref_default_page_sum) { showDefaultPageOptions = true } if (showDefaultPageOptions) { AlertDialog(onDismissRequest = { showDefaultPageOptions = false }, title = { Text(stringResource(R.string.pref_default_page), style = MaterialTheme.typography.titleLarge) }, @@ -704,23 +551,14 @@ class PreferenceActivity : AppCompatActivity() { dismissButton = { TextButton(onClick = { showDefaultPageOptions = false }) { Text(text = "Cancel") } } ) } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_back_button_opens_drawer), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_back_button_opens_drawer_summary), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefBackButtonOpensDrawer.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefBackButtonOpensDrawer.name, it).apply() - }) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - (activity as PreferenceActivity).openScreen(Screens.preferences_swipe) - })) { - Text(stringResource(R.string.swipeactions_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.swipeactions_summary), color = textColor) - } + TitleSummarySwitchPrefRow(R.string.pref_back_button_opens_drawer, R.string.pref_back_button_opens_drawer_summary, UserPreferences.Prefs.prefBackButtonOpensDrawer.name) + TitleSummaryActionColumn(R.string.swipeactions_label, R.string.swipeactions_summary) { (activity as PreferenceActivity).openScreen(Screens.preferences_swipe) } +// Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { +// (activity as PreferenceActivity).openScreen(Screens.preferences_swipe) +// })) { +// Text(stringResource(R.string.swipeactions_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) +// Text(stringResource(R.string.swipeactions_summary), color = textColor) +// } } } } @@ -738,36 +576,6 @@ class PreferenceActivity : AppCompatActivity() { remember(R.string.remember_last_page); } -// fun drawerPreferencesDialog(context: Context, callback: Runnable?) { -// val hiddenItems = hiddenDrawerItems.map { it.trim() }.toMutableSet() -// val navTitles = navMap.values.map { context.resources.getString(it.nameRes).trim() }.toTypedArray() -// val checked = BooleanArray(navMap.size) -// for (i in navMap.keys.indices) { -// val tag = navMap.keys.toList()[i] -// if (!hiddenItems.contains(tag)) checked[i] = true -// } -// val builder = MaterialAlertDialogBuilder(context) -// builder.setTitle(R.string.drawer_preferences) -// builder.setMultiChoiceItems(navTitles, checked) { _: DialogInterface?, which: Int, isChecked: Boolean -> -// if (isChecked) hiddenItems.remove(navMap.keys.toList()[which]) -// else hiddenItems.add((navMap.keys.toList()[which]).trim()) -// } -// builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> -// hiddenDrawerItems = hiddenItems.toList() -// if (hiddenItems.contains(defaultPage)) { -// for (tag in navMap.keys) { -// if (!hiddenItems.contains(tag)) { -// defaultPage = tag -// break -// } -// } -// } -// callback?.run() -// } -// builder.setNegativeButton(R.string.cancel_label, null) -// builder.create().show() -// } - private fun showFullNotificationButtonsDialog() { val context: Context? = activity @@ -812,11 +620,8 @@ class PreferenceActivity : AppCompatActivity() { builder.setPositiveButton(R.string.confirm_label, null) builder.setNegativeButton(R.string.cancel_label, null) val dialog = builder.create() - dialog.show() - val positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) - positiveButton.setOnClickListener { if (preferredButtons.size != exactItems) { val selectionView = dialog.listView @@ -891,38 +696,9 @@ class PreferenceActivity : AppCompatActivity() { }) } if (prefUnpauseOnHeadsetReconnect) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_unpauseOnHeadsetReconnect_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_unpauseOnHeadsetReconnect_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, it).apply() }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_unpauseOnBluetoothReconnect_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_unpauseOnBluetoothReconnect_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, it).apply() }) - } + TitleSummarySwitchPrefRow(R.string.pref_unpauseOnHeadsetReconnect_title, R.string.pref_unpauseOnHeadsetReconnect_sum, UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name) + TitleSummarySwitchPrefRow(R.string.pref_unpauseOnBluetoothReconnect_title, R.string.pref_unpauseOnBluetoothReconnect_sum, UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name) } - // not used -// Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { -// Column(modifier = Modifier.weight(1f)) { -// Text(stringResource(R.string.pref_pausePlaybackForFocusLoss_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) -// Text(stringResource(R.string.pref_pausePlaybackForFocusLoss_sum), color = textColor) -// } -// var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefPauseForFocusLoss.name, true)) } -// Switch(checked = isChecked, onCheckedChange = { -// isChecked = it -// appPrefs.edit().putBoolean(UserPreferences.Prefs.prefPauseForFocusLoss.name, it).apply() }) -// } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) Text(stringResource(R.string.playback_control), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { @@ -974,10 +750,7 @@ class PreferenceActivity : AppCompatActivity() { var showSpeedDialog by remember { mutableStateOf(false) } if (showSpeedDialog) PlaybackSpeedDialog(listOf(), initSpeed = prefPlaybackSpeed, maxSpeed = 3f, isGlobal = true, onDismiss = { showSpeedDialog = false }) { speed -> UserPreferences.setPlaybackSpeed(speed) } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showSpeedDialog = true })) { - Text(stringResource(R.string.playback_speed), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_playback_speed_sum), color = textColor) - } + TitleSummaryActionColumn(R.string.playback_speed, R.string.pref_playback_speed_sum) { showSpeedDialog = true } var showFBSpeedDialog by remember { mutableStateOf(false) } if (showFBSpeedDialog) PlaybackSpeedDialog(listOf(), initSpeed = fallbackSpeed, maxSpeed = 3f, isGlobal = true, onDismiss = { showFBSpeedDialog = false }) { speed -> @@ -988,20 +761,8 @@ class PreferenceActivity : AppCompatActivity() { } fallbackSpeed = round(100 * speed_) / 100f } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showFBSpeedDialog = true })) { - Text(stringResource(R.string.pref_fallback_speed), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_fallback_speed_sum), color = textColor) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_playback_time_respects_speed_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_playback_time_respects_speed_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefPlaybackTimeRespectsSpeed.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefPlaybackTimeRespectsSpeed.name, it).apply() }) - } + TitleSummaryActionColumn(R.string.pref_fallback_speed, R.string.pref_fallback_speed_sum) { showFBSpeedDialog = true } + TitleSummarySwitchPrefRow(R.string.pref_playback_time_respects_speed_title, R.string.pref_playback_time_respects_speed_sum, UserPreferences.Prefs.prefPlaybackTimeRespectsSpeed.name) var showFFSpeedDialog by remember { mutableStateOf(false) } if (showFFSpeedDialog) PlaybackSpeedDialog(listOf(), initSpeed = speedforwardSpeed, maxSpeed = 10f, isGlobal = true, onDismiss = { showFFSpeedDialog = false }) { speed -> @@ -1012,52 +773,16 @@ class PreferenceActivity : AppCompatActivity() { } speedforwardSpeed = round(10 * speed_) / 10 } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showFFSpeedDialog = true })) { - Text(stringResource(R.string.pref_speed_forward), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_speed_forward_sum), color = textColor) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_stream_over_download_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_stream_over_download_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefStreamOverDownload.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefStreamOverDownload.name, it).apply() }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_low_quality_on_mobile_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_low_quality_on_mobile_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefLowQualityOnMobile.name, false)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefLowQualityOnMobile.name, it).apply() }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_use_adaptive_progress_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_use_adaptive_progress_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefUseAdaptiveProgressUpdate.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefUseAdaptiveProgressUpdate.name, it).apply() }) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { VideoModeDialog.showDialog(requireContext()) })) { - Text(stringResource(R.string.pref_playback_video_mode), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_playback_video_mode_sum), color = textColor) - } + TitleSummaryActionColumn(R.string.pref_speed_forward, R.string.pref_speed_forward_sum) { showFFSpeedDialog = true } + TitleSummarySwitchPrefRow(R.string.pref_stream_over_download_title, R.string.pref_stream_over_download_sum, UserPreferences.Prefs.prefStreamOverDownload.name) + TitleSummarySwitchPrefRow(R.string.pref_low_quality_on_mobile_title, R.string.pref_low_quality_on_mobile_sum, UserPreferences.Prefs.prefLowQualityOnMobile.name) + TitleSummarySwitchPrefRow(R.string.pref_use_adaptive_progress_title, R.string.pref_use_adaptive_progress_sum, UserPreferences.Prefs.prefUseAdaptiveProgressUpdate.name) + TitleSummaryActionColumn(R.string.pref_playback_video_mode, R.string.pref_playback_video_mode_sum) { VideoModeDialog.showDialog(requireContext()) } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) Text(stringResource(R.string.reassign_hardware_buttons), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) var showHardwareForwardButtonOptions by remember { mutableStateOf(false) } var tempFFSelectedOption by remember { mutableStateOf(R.string.keycode_media_fast_forward) } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showHardwareForwardButtonOptions = true })) { - Text(stringResource(R.string.pref_hardware_forward_button_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_hardware_forward_button_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.pref_hardware_forward_button_title, R.string.pref_hardware_forward_button_summary) { showHardwareForwardButtonOptions = true } if (showHardwareForwardButtonOptions) { AlertDialog(onDismissRequest = { showHardwareForwardButtonOptions = false }, title = { Text(stringResource(R.string.pref_hardware_forward_button_title), style = MaterialTheme.typography.titleLarge) }, @@ -1083,10 +808,7 @@ class PreferenceActivity : AppCompatActivity() { } var showHardwarePreviousButtonOptions by remember { mutableStateOf(false) } var tempPRSelectedOption by remember { mutableStateOf(R.string.keycode_media_rewind) } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showHardwarePreviousButtonOptions = true })) { - Text(stringResource(R.string.pref_hardware_previous_button_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_hardware_previous_button_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.pref_hardware_previous_button_title, R.string.pref_hardware_previous_button_summary) { showHardwarePreviousButtonOptions = true } if (showHardwarePreviousButtonOptions) { AlertDialog(onDismissRequest = { showHardwarePreviousButtonOptions = false }, title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = MaterialTheme.typography.titleLarge) }, @@ -1112,22 +834,10 @@ class PreferenceActivity : AppCompatActivity() { } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp).padding(top = 10.dp)) Text(stringResource(R.string.queue_label), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 15.dp)) - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_enqueue_downloaded_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_enqueue_downloaded_summary), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefEnqueueDownloaded.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefEnqueueDownloaded.name, it).apply() }) - } + TitleSummarySwitchPrefRow(R.string.pref_enqueue_downloaded_title, R.string.pref_enqueue_downloaded_summary, UserPreferences.Prefs.prefEnqueueDownloaded.name) var showEnqueueLocationOptions by remember { mutableStateOf(false) } var tempLocationOption by remember { mutableStateOf(EnqueueLocation.BACK.name) } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { showEnqueueLocationOptions = true })) { - Text(stringResource(R.string.pref_enqueue_location_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_enqueue_location_sum, tempLocationOption), color = textColor) - } + TitleSummaryActionColumn(R.string.pref_enqueue_location_title, R.string.pref_enqueue_location_sum) { showEnqueueLocationOptions = true } if (showEnqueueLocationOptions) { AlertDialog(onDismissRequest = { showEnqueueLocationOptions = false }, title = { Text(stringResource(R.string.pref_hardware_previous_button_title), style = MaterialTheme.typography.titleLarge) }, @@ -1151,37 +861,9 @@ class PreferenceActivity : AppCompatActivity() { dismissButton = { TextButton(onClick = { showEnqueueLocationOptions = false }) { Text(text = "Cancel") } } ) } - - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_followQueue_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_followQueue_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefFollowQueue.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefFollowQueue.name, it).apply() }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_skip_keeps_episodes_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_skip_keeps_episodes_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, it).apply() }) - } - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.pref_mark_played_removes_from_queue_title), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.pref_mark_played_removes_from_queue_sum), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefRemoveFromQueueMarkedPlayed.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefRemoveFromQueueMarkedPlayed.name, it).apply() }) - } + TitleSummarySwitchPrefRow(R.string.pref_followQueue_title, R.string.pref_followQueue_sum, UserPreferences.Prefs.prefFollowQueue.name) + TitleSummarySwitchPrefRow(R.string.pref_skip_keeps_episodes_title, R.string.pref_skip_keeps_episodes_sum, UserPreferences.Prefs.prefSkipKeepsEpisode.name) + TitleSummarySwitchPrefRow(R.string.pref_mark_played_removes_from_queue_title, R.string.pref_mark_played_removes_from_queue_sum, UserPreferences.Prefs.prefRemoveFromQueueMarkedPlayed.name) } } } @@ -1417,89 +1099,30 @@ class PreferenceActivity : AppCompatActivity() { val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { Text(stringResource(R.string.database), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - backupDatabaseLauncher.launch(dateStampFilename("PodciniBackup-%s.realm")) - })) { - Text(stringResource(R.string.database_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.database_export_summary), color = textColor) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - importDatabase() - })) { - Text(stringResource(R.string.database_import_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.database_import_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.database_export_label, R.string.database_export_summary) { backupDatabaseLauncher.launch(dateStampFilename("PodciniBackup-%s.realm")) } + TitleSummaryActionColumn(R.string.database_import_label, R.string.database_import_summary) { importDatabase() } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) Text(stringResource(R.string.media_files), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - exportMediaFiles() - })) { - Text(stringResource(R.string.media_files_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.media_files_export_summary), color = textColor) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - importMediaFiles() - })) { - Text(stringResource(R.string.media_files_import_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.media_files_import_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.media_files_export_label, R.string.media_files_export_summary) { exportMediaFiles() } + TitleSummaryActionColumn(R.string.media_files_import_label, R.string.media_files_import_summary) { importMediaFiles() } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) Text(stringResource(R.string.preferences), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - exportPreferences() - })) { - Text(stringResource(R.string.preferences_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.preferences_export_summary), color = textColor) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - importPreferences() - })) { - Text(stringResource(R.string.preferences_import_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.preferences_import_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.preferences_export_label, R.string.preferences_export_summary) { exportPreferences() } + TitleSummaryActionColumn(R.string.preferences_import_label, R.string.preferences_import_summary) { importPreferences() } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) Text(stringResource(R.string.opml), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - openExportPathPicker(ExportTypes.OPML, chooseOpmlExportPathLauncher, OpmlWriter()) - })) { - Text(stringResource(R.string.opml_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.opml_export_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.opml_export_label, R.string.opml_export_summary) { openExportPathPicker(ExportTypes.OPML, chooseOpmlExportPathLauncher, OpmlWriter()) } if (showOpmlImportSelectionDialog) OpmlImportSelectionDialog(readElements) { showOpmlImportSelectionDialog = false } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - try { chooseOpmlImportPathLauncher.launch("*/*") } catch (e: ActivityNotFoundException) { Log.e(TAG, "No activity found. Should never happen...") } - })) { - Text(stringResource(R.string.opml_import_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.opml_import_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.opml_import_label, R.string.opml_import_summary) { + try { chooseOpmlImportPathLauncher.launch("*/*") } catch (e: ActivityNotFoundException) { Log.e(TAG, "No activity found. Should never happen...") } } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) Text(stringResource(R.string.progress), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - openExportPathPicker(ExportTypes.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter()) - })) { - Text(stringResource(R.string.progress_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.progress_export_summary), color = textColor) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - importEpisodeProgress() - })) { - Text(stringResource(R.string.progress_import_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.progress_import_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.progress_export_label, R.string.progress_export_summary) { openExportPathPicker(ExportTypes.PROGRESS, chooseProgressExportPathLauncher, EpisodesProgressWriter()) } + TitleSummaryActionColumn(R.string.progress_import_label, R.string.progress_import_summary) { importEpisodeProgress() } HorizontalDivider(modifier = Modifier.fillMaxWidth().height(1.dp)) Text(stringResource(R.string.html), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 10.dp)) - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - openExportPathPicker(ExportTypes.HTML, chooseHtmlExportPathLauncher, HtmlWriter()) - })) { - Text(stringResource(R.string.html_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.html_export_summary), color = textColor) - } - Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp).clickable(onClick = { - openExportPathPicker(ExportTypes.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter()) - })) { - Text(stringResource(R.string.favorites_export_label), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.favorites_export_summary), color = textColor) - } + TitleSummaryActionColumn(R.string.html_export_label, R.string.html_export_summary) { openExportPathPicker(ExportTypes.HTML, chooseHtmlExportPathLauncher, HtmlWriter()) } + TitleSummaryActionColumn(R.string.favorites_export_label, R.string.favorites_export_summary) { openExportPathPicker(ExportTypes.FAVORITES, chooseFavoritesExportPathLauncher, FavoritesWriter()) } } } } @@ -1547,7 +1170,6 @@ class PreferenceActivity : AppCompatActivity() { val builder = MaterialAlertDialogBuilder(requireActivity()) builder.setTitle(R.string.preferences_import_label) builder.setMessage(R.string.preferences_import_warning) - // add a button builder.setNegativeButton(R.string.no, null) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> @@ -1556,8 +1178,6 @@ class PreferenceActivity : AppCompatActivity() { intent.addCategory(Intent.CATEGORY_DEFAULT) restorePreferencesLauncher.launch(intent) } - - // create and show the alert dialog builder.show() } @@ -1572,8 +1192,6 @@ class PreferenceActivity : AppCompatActivity() { val builder = MaterialAlertDialogBuilder(requireActivity()) builder.setTitle(R.string.media_files_import_label) builder.setMessage(R.string.media_files_import_notice) - - // add a button builder.setNegativeButton(R.string.no, null) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) @@ -1581,18 +1199,13 @@ class PreferenceActivity : AppCompatActivity() { intent.addCategory(Intent.CATEGORY_DEFAULT) restoreMediaFilesLauncher.launch(intent) } - - // create and show the alert dialog builder.show() } private fun importDatabase() { - // setup the alert builder val builder = MaterialAlertDialogBuilder(requireActivity()) builder.setTitle(R.string.realm_database_import_label) builder.setMessage(R.string.database_import_warning) - - // add a button builder.setNegativeButton(R.string.no, null) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) @@ -1602,8 +1215,6 @@ class PreferenceActivity : AppCompatActivity() { intent.addCategory(Intent.CATEGORY_OPENABLE) restoreDatabaseLauncher.launch(intent) } - - // create and show the alert dialog builder.show() } @@ -1632,12 +1243,9 @@ class PreferenceActivity : AppCompatActivity() { } private fun importEpisodeProgress() { - // setup the alert builder val builder = MaterialAlertDialogBuilder(requireActivity()) builder.setTitle(R.string.progress_import_label) builder.setMessage(R.string.progress_import_warning) - - // add a button builder.setNegativeButton(R.string.no, null) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) @@ -1646,7 +1254,6 @@ class PreferenceActivity : AppCompatActivity() { intent.addCategory(Intent.CATEGORY_OPENABLE) restoreProgressLauncher.launch(intent) } - // create and show the alert dialog builder.show() } @@ -1672,7 +1279,6 @@ class PreferenceActivity : AppCompatActivity() { private fun openExportPathPicker(exportType: ExportTypes, result: ActivityResultLauncher, writer: ExportWriter) { val title = dateStampFilename(exportType.outputNameTemplate) - val intentPickAction = Intent(Intent.ACTION_CREATE_DOCUMENT) .addCategory(Intent.CATEGORY_OPENABLE) .setType(exportType.contentType) @@ -1703,9 +1309,7 @@ class PreferenceActivity : AppCompatActivity() { try { val chosenDir = DocumentFile.fromTreeUri(context, uri) ?: throw IOException("Destination directory is not valid") val exportSubDir = chosenDir.createDirectory("Podcini-Prefs") ?: throw IOException("Error creating subdirectory Podcini-Prefs") - val sharedPreferencesDir = context.applicationContext.filesDir.parentFile?.listFiles { file -> - file.name.startsWith("shared_prefs") - }?.firstOrNull() + val sharedPreferencesDir = context.applicationContext.filesDir.parentFile?.listFiles { file -> file.name.startsWith("shared_prefs") }?.firstOrNull() if (sharedPreferencesDir != null) { sharedPreferencesDir.listFiles()!!.forEach { file -> val destFile = exportSubDir.createFile("text/xml", file.name) @@ -1744,23 +1348,15 @@ class PreferenceActivity : AppCompatActivity() { private fun copyStream(inputStream: InputStream, outputStream: OutputStream) { val buffer = ByteArray(1024) var bytesRead: Int - while (inputStream.read(buffer).also { bytesRead = it } != -1) { - outputStream.write(buffer, 0, bytesRead) - } + while (inputStream.read(buffer).also { bytesRead = it } != -1) outputStream.write(buffer, 0, bytesRead) } @Throws(IOException::class) fun importBackup(uri: Uri, context: Context) { try { val exportedDir = DocumentFile.fromTreeUri(context, uri) ?: throw IOException("Backup directory is not valid") - val sharedPreferencesDir = context.applicationContext.filesDir.parentFile?.listFiles { file -> - file.name.startsWith("shared_prefs") - }?.firstOrNull() - if (sharedPreferencesDir != null) { - sharedPreferencesDir.listFiles()?.forEach { file -> -// val prefName = file.name.substring(0, file.name.lastIndexOf('.')) - file.delete() - } - } else Log.e("Error", "shared_prefs directory not found") + val sharedPreferencesDir = context.applicationContext.filesDir.parentFile?.listFiles { file -> file.name.startsWith("shared_prefs") }?.firstOrNull() + if (sharedPreferencesDir != null) sharedPreferencesDir.listFiles()?.forEach { file -> file.delete() } + else Log.e("Error", "shared_prefs directory not found") val files = exportedDir.listFiles() var hasPodciniRPrefs = false for (file in files) { @@ -1772,7 +1368,6 @@ class PreferenceActivity : AppCompatActivity() { for (file in files) { if (file?.isFile == true && file.name?.endsWith(".xml") == true) { var destName = file.name!! -// contains info on existing widgets, no need to import if (destName.contains("PlayerWidgetPrefs")) continue // for importing from Podcini version 5 and below if (!hasPodciniRPrefs) { @@ -1973,7 +1568,6 @@ class PreferenceActivity : AppCompatActivity() { } } - /** Reads OPML documents. */ object EpisodeProgressReader { private const val TAG = "EpisodeProgressReader" @@ -2015,10 +1609,9 @@ class PreferenceActivity : AppCompatActivity() { } } - /** Writes saved favorites to file. */ class EpisodesProgressWriter : ExportWriter { @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - override fun writeDocument(feeds: List, writer: Writer?, context: Context) { + override fun writeDocument(feeds: List, writer: Writer, context: Context) { Logd(TAG, "Starting to write document") val queuedEpisodeActions: MutableList = mutableListOf() val pausedItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.paused.name), EpisodeSortOrder.DATE_NEW_OLD) @@ -2053,7 +1646,7 @@ class PreferenceActivity : AppCompatActivity() { list.put(obj) } } - writer?.write(list.toString()) + writer.write(list.toString()) } catch (e: Exception) { e.printStackTrace() throw SyncServiceException(e) @@ -2069,10 +1662,9 @@ class PreferenceActivity : AppCompatActivity() { } } - /** Writes saved favorites to file. */ class FavoritesWriter : ExportWriter { @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - override fun writeDocument(feeds: List, writer: Writer?, context: Context) { + override fun writeDocument(feeds: List, writer: Writer, context: Context) { Logd(TAG, "Starting to write document") val templateStream = context.assets.open("html-export-template.html") var template = IOUtils.toString(templateStream, UTF_8) @@ -2084,11 +1676,12 @@ class PreferenceActivity : AppCompatActivity() { val feedTemplate = IOUtils.toString(feedTemplateStream, UTF_8) val allFavorites = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.superb.name), EpisodeSortOrder.DATE_NEW_OLD) val favoritesByFeed = buildFeedMap(allFavorites) - writer!!.append(templateParts[0]) + writer.append(templateParts[0]) for (feedId in favoritesByFeed.keys) { val favorites: List = favoritesByFeed[feedId]!! + if (favorites[0].feed == null) continue writer.append("
  • \n") - writeFeed(writer, favorites[0].feed, feedTemplate) + writeFeed(writer, favorites[0].feed!!, feedTemplate) writer.append("
      \n") for (item in favorites) writeFavoriteItem(writer, item, favTemplate) writer.append("
  • \n") @@ -2114,23 +1707,23 @@ class PreferenceActivity : AppCompatActivity() { return feedMap } @Throws(IOException::class) - private fun writeFeed(writer: Writer?, feed: Feed?, feedTemplate: String) { + private fun writeFeed(writer: Writer, feed: Feed, feedTemplate: String) { val feedInfo = feedTemplate - .replace("{FEED_IMG}", feed!!.imageUrl!!) - .replace("{FEED_TITLE}", feed.title!!) - .replace("{FEED_LINK}", feed.link!!) - .replace("{FEED_WEBSITE}", feed.downloadUrl!!) - writer!!.append(feedInfo) + .replace("{FEED_IMG}", feed.imageUrl?:"") + .replace("{FEED_TITLE}", feed.title?:" No title") + .replace("{FEED_LINK}", feed.link?: "") + .replace("{FEED_WEBSITE}", feed.downloadUrl?:"") + writer.append(feedInfo) } @Throws(IOException::class) - private fun writeFavoriteItem(writer: Writer?, item: Episode, favoriteTemplate: String) { + private fun writeFavoriteItem(writer: Writer, item: Episode, favoriteTemplate: String) { var favItem = favoriteTemplate.replace("{FAV_TITLE}", item.title!!.trim { it <= ' ' }) favItem = if (item.link != null) favItem.replace("{FAV_WEBSITE}", item.link!!) else favItem.replace("{FAV_WEBSITE}", "") favItem = if (item.media != null && item.media!!.downloadUrl != null) favItem.replace("{FAV_MEDIA}", item.media!!.downloadUrl!!) else favItem.replace("{FAV_MEDIA}", "") - writer!!.append(favItem) + writer.append(favItem) } override fun fileExtension(): String { return "html" @@ -2149,13 +1742,13 @@ class PreferenceActivity : AppCompatActivity() { * Takes a list of feeds and a writer and writes those into an HTML document. */ @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - override fun writeDocument(feeds: List, writer: Writer?, context: Context) { + override fun writeDocument(feeds: List, writer: Writer, context: Context) { Logd(TAG, "Starting to write document") val templateStream = context.assets.open("html-export-template.html") var template = IOUtils.toString(templateStream, "UTF-8") template = template.replace("\\{TITLE\\}".toRegex(), "Subscriptions") val templateParts = template.split("\\{FEEDS\\}".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - writer!!.append(templateParts[0]) + writer.append(templateParts[0]) for (feed in feeds) { writer.append("
  • (Prefs.preference_instant_sync.name) -// preferenceInstantSync!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// WifiAuthenticationFragment().show(childFragmentManager, WifiAuthenticationFragment.TAG) -// true -// } -// -// val loggedIn = isProviderConnected -// val preferenceHeader = findPreference(Prefs.preference_synchronization_description.name) -// if (loggedIn) { -// val selectedProvider = SynchronizationProviderViewData.fromIdentifier(selectedSyncProviderKey) -// preferenceHeader!!.title = "" -// if (selectedProvider != null) { -// preferenceHeader.setSummary(selectedProvider.summaryResource) -// preferenceHeader.setIcon(selectedProvider.iconResource) -// } -// preferenceHeader.onPreferenceClickListener = null -// } else { -// preferenceHeader!!.setTitle(R.string.synchronization_choose_title) -// preferenceHeader.setSummary(R.string.synchronization_summary_unchoosen) -// preferenceHeader.setIcon(R.drawable.ic_cloud) -// preferenceHeader.onPreferenceClickListener = Preference.OnPreferenceClickListener { -// chooseProviderAndLogin() -// true -// } -// } -// -// val gpodnetSetLoginPreference = findPreference(Prefs.pref_gpodnet_setlogin_information.name) -// gpodnetSetLoginPreference!!.isVisible = isProviderSelected(SynchronizationProviderViewData.GPODDER_NET) -// gpodnetSetLoginPreference.isEnabled = loggedIn -// findPreference(Prefs.pref_synchronization_sync.name)!!.isVisible = loggedIn -// findPreference(Prefs.pref_synchronization_force_full_sync.name)!!.isVisible = loggedIn -// findPreference(Prefs.pref_synchronization_logout.name)!!.isVisible = loggedIn -// if (loggedIn) { -// val summary = getString(R.string.synchronization_login_status, -// SynchronizationCredentials.username, SynchronizationCredentials.hosturl) -// val formattedSummary = HtmlCompat.fromHtml(summary, HtmlCompat.FROM_HTML_MODE_LEGACY) -// findPreference(Prefs.pref_synchronization_logout.name)!!.summary = formattedSummary -// updateLastSyncReport(SynchronizationSettings.isLastSyncSuccessful, SynchronizationSettings.lastSyncAttempt) -// } else { -// findPreference(Prefs.pref_synchronization_logout.name)?.summary = "" -// (activity as PreferenceActivity).supportActionBar?.setSubtitle("") -// } -// } - private fun chooseProviderAndLogin() { val builder = MaterialAlertDialogBuilder(requireContext()) builder.setTitle(R.string.dialog_choose_sync_service_title) @@ -2870,12 +2327,9 @@ class PreferenceActivity : AppCompatActivity() { builder.setAdapter(adapter) { _: DialogInterface?, which: Int -> when (providers[which]) { - SynchronizationProviderViewData.GPODDER_NET -> GpodderAuthenticationFragment().show(childFragmentManager, - GpodderAuthenticationFragment.TAG) - SynchronizationProviderViewData.NEXTCLOUD_GPODDER -> NextcloudAuthenticationFragment().show(childFragmentManager, - NextcloudAuthenticationFragment.TAG) + SynchronizationProviderViewData.GPODDER_NET -> GpodderAuthenticationFragment().show(childFragmentManager, GpodderAuthenticationFragment.TAG) + SynchronizationProviderViewData.NEXTCLOUD_GPODDER -> NextcloudAuthenticationFragment().show(childFragmentManager, NextcloudAuthenticationFragment.TAG) } -// updateScreen() loggedIn = isProviderConnected } @@ -2896,9 +2350,6 @@ class PreferenceActivity : AppCompatActivity() { (activity as PreferenceActivity).supportActionBar!!.subtitle = status } - /** - * Displays a dialog with a username and password text field and an optional checkbox to save username and preferences. - */ abstract class AuthenticationDialog(context: Context, titleRes: Int, enableUsernameField: Boolean, usernameInitialValue: String?, passwordInitialValue: String?) : MaterialAlertDialogBuilder(context) { @@ -2932,7 +2383,6 @@ class PreferenceActivity : AppCompatActivity() { } protected open fun onCancelled() {} - protected abstract fun onConfirmed(username: String, password: String) } @@ -2983,7 +2433,6 @@ class PreferenceActivity : AppCompatActivity() { override fun onResume() { super.onResume() nextcloudLoginFlow?.onResume() - if (shouldDismiss) dismiss() } override fun onNextcloudAuthenticated(server: String, username: String, password: String) { @@ -3331,9 +2780,7 @@ class PreferenceActivity : AppCompatActivity() { Toast.makeText(requireContext(), R.string.sync_status_success, Toast.LENGTH_LONG).show() dialog?.dismiss() } - R.string.sync_status_in_progress -> { - binding!!.progressBar.progress = event.message.toInt() - } + R.string.sync_status_in_progress -> binding!!.progressBar.progress = event.message.toInt() else -> { Logd(TAG, "Sync result unknow ${event.messageResId}") // Toast.makeText(context, "Sync result unknow ${event.messageResId}", Toast.LENGTH_LONG).show() @@ -3359,30 +2806,9 @@ class PreferenceActivity : AppCompatActivity() { val scrollState = rememberScrollState() Column(modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp).verticalScroll(scrollState)) { Text(stringResource(R.string.notification_group_errors), color = textColor, style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold) - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.notification_channel_download_error), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.notification_channel_download_error_description), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.prefShowDownloadReport.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.prefShowDownloadReport.name, it).apply() - }) - } - if (isProviderConnected) { - Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 10.dp)) { - Column(modifier = Modifier.weight(1f)) { - Text(stringResource(R.string.notification_channel_sync_error), color = textColor, style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold) - Text(stringResource(R.string.notification_channel_sync_error_description), color = textColor) - } - var isChecked by remember { mutableStateOf(appPrefs.getBoolean(UserPreferences.Prefs.pref_gpodnet_notifications.name, true)) } - Switch(checked = isChecked, onCheckedChange = { - isChecked = it - appPrefs.edit().putBoolean(UserPreferences.Prefs.pref_gpodnet_notifications.name, it).apply() - }) - } - } + TitleSummarySwitchPrefRow(R.string.notification_channel_download_error, R.string.notification_channel_download_error_description, UserPreferences.Prefs.prefShowDownloadReport.name) + if (isProviderConnected) + TitleSummarySwitchPrefRow(R.string.notification_channel_sync_error, R.string.notification_channel_sync_error_description, UserPreferences.Prefs.pref_gpodnet_notifications.name) } } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt deleted file mode 100644 index c9db5fdd..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt +++ /dev/null @@ -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) - } -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt index c59d50ba..2fd6106c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/Composables.kt @@ -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() }) } -} \ No newline at end of file +} + diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 04f3ff01..065c865f 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -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() { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt deleted file mode 100644 index 24f20ec6..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdater.kt +++ /dev/null @@ -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" - } - } - -} diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt deleted file mode 100644 index 91c85a74..00000000 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt +++ /dev/null @@ -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) - } - } -} diff --git a/app/src/main/res/layout/activity_widget_config.xml b/app/src/main/res/layout/activity_widget_config.xml deleted file mode 100644 index 997d6f0a..00000000 --- a/app/src/main/res/layout/activity_widget_config.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -