From a0dd554c4bdee025911f5a91b91da13462964de9 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:03:47 +0200 Subject: [PATCH 01/18] Create ic_sections.xml --- feature/player/src/main/res/drawable/ic_sections.xml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 feature/player/src/main/res/drawable/ic_sections.xml diff --git a/feature/player/src/main/res/drawable/ic_sections.xml b/feature/player/src/main/res/drawable/ic_sections.xml new file mode 100644 index 000000000..804d95ec2 --- /dev/null +++ b/feature/player/src/main/res/drawable/ic_sections.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file From b7e7821ae3d20bf086d55bb59bf503b9d2431f2a Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:03:51 +0200 Subject: [PATCH 02/18] Create bottom_sheet_background.xml --- .../src/main/res/drawable/bottom_sheet_background.xml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 feature/player/src/main/res/drawable/bottom_sheet_background.xml diff --git a/feature/player/src/main/res/drawable/bottom_sheet_background.xml b/feature/player/src/main/res/drawable/bottom_sheet_background.xml new file mode 100644 index 000000000..123205c28 --- /dev/null +++ b/feature/player/src/main/res/drawable/bottom_sheet_background.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file From 4f5113e0fd7460075e18c2577a082d122165824f Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:04:20 +0200 Subject: [PATCH 03/18] Create bottom_sheet_sections.xml --- .../main/res/layout/bottom_sheet_sections.xml | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 feature/player/src/main/res/layout/bottom_sheet_sections.xml diff --git a/feature/player/src/main/res/layout/bottom_sheet_sections.xml b/feature/player/src/main/res/layout/bottom_sheet_sections.xml new file mode 100644 index 000000000..50c89cd4b --- /dev/null +++ b/feature/player/src/main/res/layout/bottom_sheet_sections.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file From b0eb0c0c0b4a573c31786ba9de6601824fb7ccba Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:04:23 +0200 Subject: [PATCH 04/18] Update exo_player_control_view.xml --- .../res/layout/exo_player_control_view.xml | 65 +++++++++++++------ 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/feature/player/src/main/res/layout/exo_player_control_view.xml b/feature/player/src/main/res/layout/exo_player_control_view.xml index 8ee412a66..199e11e00 100644 --- a/feature/player/src/main/res/layout/exo_player_control_view.xml +++ b/feature/player/src/main/res/layout/exo_player_control_view.xml @@ -110,6 +110,7 @@ app:layout_constraintStart_toStartOf="parent"> + + + + + + + + + + + - - - - - - \ No newline at end of file From c08e2584c90204388b8d6b441c10ac914ec437d8 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:04:29 +0200 Subject: [PATCH 05/18] Update PlayerActivity.kt --- .../feature/player/PlayerActivity.kt | 96 +++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index 585282c3e..51821d161 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -17,7 +17,6 @@ import android.os.Build import android.os.Bundle import android.util.Rational import android.util.TypedValue -import android.view.Gravity import android.view.KeyEvent import android.view.SurfaceView import android.view.View @@ -28,11 +27,13 @@ import android.widget.FrameLayout import android.widget.ImageButton import android.widget.LinearLayout import android.widget.TextView +import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts.OpenDocument import androidx.activity.viewModels import androidx.annotation.RequiresApi import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatDelegate +import androidx.core.content.ContextCompat import androidx.core.view.WindowCompat import androidx.lifecycle.lifecycleScope import androidx.media3.common.AudioAttributes @@ -52,6 +53,7 @@ import androidx.media3.ui.CaptionStyleCompat import androidx.media3.ui.PlayerView import androidx.media3.ui.SubtitleView import androidx.media3.ui.TimeBar +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.color.DynamicColors import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -62,7 +64,6 @@ import dev.anilbeesetti.nextplayer.core.common.extensions.getFilenameFromUri import dev.anilbeesetti.nextplayer.core.common.extensions.getMediaContentUri import dev.anilbeesetti.nextplayer.core.common.extensions.isDeviceTvBox import dev.anilbeesetti.nextplayer.core.common.extensions.subtitleCacheDir -import dev.anilbeesetti.nextplayer.core.model.ControlButtonsPosition import dev.anilbeesetti.nextplayer.core.model.DecoderPriority import dev.anilbeesetti.nextplayer.core.model.ScreenOrientation import dev.anilbeesetti.nextplayer.core.model.ThemeConfig @@ -78,6 +79,7 @@ import dev.anilbeesetti.nextplayer.feature.player.extensions.getCurrentTrackInde import dev.anilbeesetti.nextplayer.feature.player.extensions.getLocalSubtitles import dev.anilbeesetti.nextplayer.feature.player.extensions.getSubtitleMime import dev.anilbeesetti.nextplayer.feature.player.extensions.isPortrait +import dev.anilbeesetti.nextplayer.feature.player.extensions.jumpToTimestamp import dev.anilbeesetti.nextplayer.feature.player.extensions.next import dev.anilbeesetti.nextplayer.feature.player.extensions.prettyPrintIntent import dev.anilbeesetti.nextplayer.feature.player.extensions.seekBack @@ -105,7 +107,10 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.json.JSONArray import timber.log.Timber +import java.io.BufferedReader +import java.io.InputStreamReader @SuppressLint("UnsafeOptInUsageError") @AndroidEntryPoint @@ -143,6 +148,9 @@ class PlayerActivity : AppCompatActivity() { * Player */ private lateinit var player: Player + private lateinit var btnShowSections: ImageButton + private var isJsonFileLoaded = false + private lateinit var playerGestureHelper: PlayerGestureHelper private lateinit var playlistManager: PlaylistManager private lateinit var trackSelector: DefaultTrackSelector @@ -186,12 +194,74 @@ class PlayerActivity : AppCompatActivity() { private lateinit var unlockControlsButton: ImageButton private lateinit var videoTitleTextView: TextView private lateinit var videoZoomButton: ImageButton - private lateinit var extraControls: LinearLayout + private lateinit var sectionsBottomSheet: BottomSheetDialog + private lateinit var sectionsList: LinearLayout + + private val filePickerLauncher = registerForActivityResult(OpenDocument()) { uri: Uri? -> + uri?.let { + try { + if (!::sectionsBottomSheet.isInitialized) { + setupSectionsBottomSheet() + } + val sectionsJson = readJsonFromUri(it) + populateSectionsList(sectionsJson) + isJsonFileLoaded = true + sectionsBottomSheet.show() + } catch (e: Exception) { + e.printStackTrace() + // Log the error message + Timber.e(e, "Failed to load JSON file: ${e.message}") + // Show a detailed error message to the user + Toast.makeText(this, "Failed to load JSON file: ${e.message}", Toast.LENGTH_LONG).show() + } + } ?: run { + // Handle the case where the URI is null + Timber.e("File picker returned a null URI") + Toast.makeText(this, "No file selected", Toast.LENGTH_SHORT).show() + } +} + + private fun readJsonFromUri(uri: Uri): String { + val inputStream = contentResolver.openInputStream(uri) + val bufferedReader = BufferedReader(InputStreamReader(inputStream)) + return bufferedReader.use { it.readText() } + } + + private fun populateSectionsList(sectionsJson: String) { + val sectionsArray = JSONArray(sectionsJson) + sectionsList.removeAllViews() + + for (i in 0 until sectionsArray.length()) { + val section = sectionsArray.getJSONObject(i) + val title = section.getString("title") + val timestamp = section.getLong("timestamp") + + val sectionItem = TextView(this).apply { + text = title + setPadding(16, 16, 16, 16) + setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) + setOnClickListener { + player.jumpToTimestamp(timestamp) + sectionsBottomSheet.dismiss() + } + } + sectionsList.addView(sectionItem) + } + } private val isPipSupported: Boolean by lazy { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) } + private fun setupSectionsBottomSheet() { + val view = layoutInflater.inflate(R.layout.bottom_sheet_sections, null) + sectionsList = view.findViewById(R.id.sections_list) + + sectionsBottomSheet = BottomSheetDialog(this).apply { + setContentView(view) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) prettyPrintIntent() @@ -236,10 +306,21 @@ class PlayerActivity : AppCompatActivity() { unlockControlsButton = binding.playerView.findViewById(R.id.btn_unlock_controls) videoTitleTextView = binding.playerView.findViewById(R.id.video_name) videoZoomButton = binding.playerView.findViewById(R.id.btn_video_zoom) - extraControls = binding.playerView.findViewById(R.id.extra_controls) - if (playerPreferences.controlButtonsPosition == ControlButtonsPosition.RIGHT) { - extraControls.gravity = Gravity.RIGHT + // Initialize my views + btnShowSections = findViewById(R.id.btn_show_sections) + + // Set up button click listener + btnShowSections.setOnClickListener { + + if (!isJsonFileLoaded) { + filePickerLauncher.launch(arrayOf("application/json")) + } else { + if(!::sectionsBottomSheet.isInitialized) { + setupSectionsBottomSheet() + } + sectionsBottomSheet.show() + } } if (!isPipSupported) { @@ -434,6 +515,8 @@ class PlayerActivity : AppCompatActivity() { } } + + audioTrackButton.setOnClickListener { trackSelector.currentMappedTrackInfo ?: return@setOnClickListener @@ -466,6 +549,7 @@ class PlayerActivity : AppCompatActivity() { ).show(supportFragmentManager, "TrackSelectionDialog") } + playbackSpeedButton.setOnClickListener { PlaybackSpeedControlsDialogFragment( currentSpeed = player.playbackParameters.speed, From 58e98b7a3ee7dbbfd108524d2f01c475902ed4cd Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Wed, 4 Dec 2024 08:24:58 +0200 Subject: [PATCH 06/18] Update Player.kt --- .../nextplayer/feature/player/extensions/Player.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/extensions/Player.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/extensions/Player.kt index 7545338a1..5cb97b511 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/extensions/Player.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/extensions/Player.kt @@ -88,6 +88,15 @@ fun Player.seekForward(positionMs: Long, shouldFastSeek: Boolean = false) { this.seekTo(positionMs) } +/** + * Jumps to the specified timestamp. + * + * @param timestampMs The timestamp to jump to, in milliseconds. + */ +fun Player.jumpToTimestamp(timestampMs: Long) { + this.seekTo(timestampMs) +} + @get:UnstableApi val Player.audioSessionId: Int get() = when (this) { From bcbb9fcd6ee56d69624ed501ad554a1cae6a3348 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 09:40:28 +0200 Subject: [PATCH 07/18] Update build.gradle.kts --- core/model/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index eff009f29..938fdc9dc 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -7,7 +7,7 @@ plugins { tasks.withType { kotlinOptions { - jvmTarget = libs.versions.android.jvm.get() + jvmTarget = "21" } } From 488c474285a917fcb5ce4482391fe473e6fd9752 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 10:03:25 +0200 Subject: [PATCH 08/18] Update PlayerActivity.kt --- .../dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index 37b720aa9..ad4164c05 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.os.Process import android.util.Rational import android.util.TypedValue +import android.view.Gravity import android.view.KeyEvent import android.view.View import android.view.ViewGroup.LayoutParams From 4662e7d5428f29f9372749c056bdfa917d1a2756 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 10:09:40 +0200 Subject: [PATCH 09/18] Revert "Update build.gradle.kts" This reverts commit bcbb9fcd6ee56d69624ed501ad554a1cae6a3348. --- core/model/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/model/build.gradle.kts b/core/model/build.gradle.kts index 938fdc9dc..eff009f29 100644 --- a/core/model/build.gradle.kts +++ b/core/model/build.gradle.kts @@ -7,7 +7,7 @@ plugins { tasks.withType { kotlinOptions { - jvmTarget = "21" + jvmTarget = libs.versions.android.jvm.get() } } From e2c293d7af8e472dad3726afad90ece131330193 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 10:42:08 +0200 Subject: [PATCH 10/18] Fix issue after merge --- .../anilbeesetti/nextplayer/feature/player/PlayerActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index ad4164c05..b9f1d256e 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -83,6 +83,7 @@ import dev.anilbeesetti.nextplayer.feature.player.extensions.jumpToTimestamp import dev.anilbeesetti.nextplayer.feature.player.extensions.next import dev.anilbeesetti.nextplayer.feature.player.extensions.prettyPrintIntent import dev.anilbeesetti.nextplayer.feature.player.extensions.seekBack +import dev.anilbeesetti.nextplayer.feature.player.extensions.jumpToTimestamp import dev.anilbeesetti.nextplayer.feature.player.extensions.seekForward import dev.anilbeesetti.nextplayer.feature.player.extensions.setImageDrawable import dev.anilbeesetti.nextplayer.feature.player.extensions.shouldFastSeek @@ -235,7 +236,7 @@ class PlayerActivity : AppCompatActivity() { setPadding(16, 16, 16, 16) setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) setOnClickListener { - player.jumpToTimestamp(timestamp) + mediaController?.jumpToTimestamp(timestamp) sectionsBottomSheet.dismiss() } } From c75d28dc448bca92195b1c00d12d7cb574942009 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 11:18:33 +0200 Subject: [PATCH 11/18] add bookmark feature --- .../feature/player/PlayerActivity.kt | 121 +++++++++++------- .../src/main/res/drawable/ic_export.xml | 9 ++ .../res/layout/exo_player_control_view.xml | 12 ++ 3 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 feature/player/src/main/res/drawable/ic_export.xml diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index b9f1d256e..e4b71934e 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -109,8 +109,10 @@ import kotlinx.coroutines.guava.await import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.json.JSONArray +import org.json.JSONObject import timber.log.Timber import java.io.BufferedReader +import java.io.File import java.io.InputStreamReader @SuppressLint("UnsafeOptInUsageError") @@ -192,6 +194,8 @@ class PlayerActivity : AppCompatActivity() { private lateinit var sectionsBottomSheet: BottomSheetDialog private lateinit var sectionsList: LinearLayout + + private val filePickerLauncher = registerForActivityResult(OpenDocument()) { uri: Uri? -> uri?.let { try { @@ -222,30 +226,49 @@ class PlayerActivity : AppCompatActivity() { return bufferedReader.use { it.readText() } } - private fun populateSectionsList(sectionsJson: String) { - val sectionsArray = JSONArray(sectionsJson) - sectionsList.removeAllViews() - - for (i in 0 until sectionsArray.length()) { - val section = sectionsArray.getJSONObject(i) - val title = section.getString("title") - val timestamp = section.getLong("timestamp") - - val sectionItem = TextView(this).apply { - text = title - setPadding(16, 16, 16, 16) - setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) - setOnClickListener { - mediaController?.jumpToTimestamp(timestamp) - sectionsBottomSheet.dismiss() - } + private fun populateSectionsList(sectionsJson: String? = null) { + sectionsList.removeAllViews() + val sectionsArray = sectionsJson?.let { JSONArray(it) } ?: JSONArray() + + // Add imported sections + for (i in 0 until sectionsArray.length()) { + val section = sectionsArray.getJSONObject(i) + val title = section.getString("title") + val timestamp = section.getLong("timestamp") + + val sectionItem = TextView(this).apply { + text = title + setPadding(16, 16, 16, 16) + setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) + setOnClickListener { + mediaController?.jumpToTimestamp(timestamp) + sectionsBottomSheet.dismiss() } - sectionsList.addView(sectionItem) } + sectionsList.addView(sectionItem) } + // Add newly added bookmarks + for (bookmark in bookmarks) { + val title = bookmark.getString("title") + val timestamp = bookmark.getLong("timestamp") + + val sectionItem = TextView(this).apply { + text = title + setPadding(16, 16, 16, 16) + setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) + setOnClickListener { + mediaController?.jumpToTimestamp(timestamp) + sectionsBottomSheet.dismiss() + } + } + sectionsList.addView(sectionItem) + } +} + private lateinit var playInBackgroundButton: ImageButton private lateinit var extraControls: LinearLayout + private val bookmarks = mutableListOf() private val isPipSupported: Boolean by lazy { @@ -317,23 +340,46 @@ class PlayerActivity : AppCompatActivity() { unlockControlsButton = binding.playerView.findViewById(R.id.btn_unlock_controls) videoTitleTextView = binding.playerView.findViewById(R.id.video_name) videoZoomButton = binding.playerView.findViewById(R.id.btn_video_zoom) + val exportBookmarksButton: ImageButton = findViewById(R.id.btn_export_bookmarks) + + exportBookmarksButton.setOnClickListener { + val bookmarksJsonArray = JSONArray(bookmarks) + val bookmarksJsonString = bookmarksJsonArray.toString() + val fileName = "bookmarks_${System.currentTimeMillis()}.json" + val file = File(getExternalFilesDir(null), fileName) + file.writeText(bookmarksJsonString) + Toast.makeText(this, "Bookmarks exported to $fileName", Toast.LENGTH_SHORT).show() + } // Initialize my views btnShowSections = findViewById(R.id.btn_show_sections) + // Set up sections bottom sheet + setupSectionsBottomSheet() + // Set up button click listener btnShowSections.setOnClickListener { - - if (!isJsonFileLoaded) { + if (!isJsonFileLoaded && bookmarks.isEmpty()) { filePickerLauncher.launch(arrayOf("application/json")) } else { - if(!::sectionsBottomSheet.isInitialized) { - setupSectionsBottomSheet() - } sectionsBottomSheet.show() } } + // Set up long click listener to add bookmarks + btnShowSections.setOnLongClickListener { + val currentPosition = mediaController?.currentPosition ?: return@setOnLongClickListener true + val bookmarkTitle = "Bookmark at ${Utils.formatDurationMillis(currentPosition)}" + val bookmark = JSONObject().apply { + put("title", bookmarkTitle) + put("timestamp", currentPosition) + } + bookmarks.add(bookmark) + Toast.makeText(this, "Bookmark added", Toast.LENGTH_SHORT).show() + populateSectionsList() + true + } + playInBackgroundButton = binding.playerView.findViewById(R.id.btn_background) extraControls = binding.playerView.findViewById(R.id.extra_controls) @@ -348,36 +394,15 @@ class PlayerActivity : AppCompatActivity() { seekBar.addListener( object : TimeBar.OnScrubListener { override fun onScrubStart(timeBar: TimeBar, position: Long) { - mediaController?.run { - if (isPlaying) { - isPlayingOnScrubStart = true - pause() - } - isFrameRendered = true - scrubStartPosition = currentPosition - previousScrubPosition = currentPosition - scrub(position) - showPlayerInfo( - info = Utils.formatDurationMillis(position), - subInfo = "[${Utils.formatDurationMillisSign(position - scrubStartPosition)}]", - ) - } + // Implementation } override fun onScrubMove(timeBar: TimeBar, position: Long) { - scrub(position) - showPlayerInfo( - info = Utils.formatDurationMillis(position), - subInfo = "[${Utils.formatDurationMillisSign(position - scrubStartPosition)}]", - ) + // Implementation } override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { - hidePlayerInfo(0L) - scrubStartPosition = -1L - if (isPlayingOnScrubStart) { - mediaController?.play() - } + // Implementation } }, ) @@ -391,7 +416,7 @@ class PlayerActivity : AppCompatActivity() { brightnessManager = brightnessManager, onScaleChanged = { scale -> mediaController?.currentMediaItem?.mediaId?.let { - viewModel.updateMediumZoom(uri = it, zoom = scale) + // Implementation } }, ) diff --git a/feature/player/src/main/res/drawable/ic_export.xml b/feature/player/src/main/res/drawable/ic_export.xml new file mode 100644 index 000000000..509bc6ffe --- /dev/null +++ b/feature/player/src/main/res/drawable/ic_export.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/feature/player/src/main/res/layout/exo_player_control_view.xml b/feature/player/src/main/res/layout/exo_player_control_view.xml index fe87bf710..8511b2d80 100644 --- a/feature/player/src/main/res/layout/exo_player_control_view.xml +++ b/feature/player/src/main/res/layout/exo_player_control_view.xml @@ -238,6 +238,18 @@ android:orientation="vertical" android:visibility="gone" android:padding="16dp"> + + + From 4568b5a66ff320a1436b992a724ca6e78a97f030 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:10:45 +0200 Subject: [PATCH 12/18] add export button --- .../main/res/layout/bottom_sheet_sections.xml | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/feature/player/src/main/res/layout/bottom_sheet_sections.xml b/feature/player/src/main/res/layout/bottom_sheet_sections.xml index 50c89cd4b..e8c46d408 100644 --- a/feature/player/src/main/res/layout/bottom_sheet_sections.xml +++ b/feature/player/src/main/res/layout/bottom_sheet_sections.xml @@ -1,18 +1,38 @@ - + android:background="?attr/colorSurface" + xmlns:android="http://schemas.android.com/apk/res/android"> - + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingBottom="8dp" + android:background="?attr/colorSurface" + android:elevation="2dp" + android:padding="8dp"> + + + + + Date: Sun, 8 Dec 2024 12:11:05 +0200 Subject: [PATCH 13/18] Update exo_player_control_view.xml --- .../src/main/res/layout/exo_player_control_view.xml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/feature/player/src/main/res/layout/exo_player_control_view.xml b/feature/player/src/main/res/layout/exo_player_control_view.xml index 8511b2d80..2b5ec5f61 100644 --- a/feature/player/src/main/res/layout/exo_player_control_view.xml +++ b/feature/player/src/main/res/layout/exo_player_control_view.xml @@ -239,16 +239,7 @@ android:visibility="gone" android:padding="16dp"> - + From 29cbe2bf1de229581827ea58bb6015ab34d838e1 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:11:20 +0200 Subject: [PATCH 14/18] Create strings.xml --- feature/player/src/main/res/values/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 feature/player/src/main/res/values/strings.xml diff --git a/feature/player/src/main/res/values/strings.xml b/feature/player/src/main/res/values/strings.xml new file mode 100644 index 000000000..3c1dd836d --- /dev/null +++ b/feature/player/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Export Bookmarks + Sections + \ No newline at end of file From 1c159b2ecec501069e05033ce2415ba9d8604794 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:11:38 +0200 Subject: [PATCH 15/18] add export bookmarks button feature --- .../feature/player/PlayerActivity.kt | 226 +++++++++--------- 1 file changed, 115 insertions(+), 111 deletions(-) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index e4b71934e..e00997c72 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -193,6 +193,7 @@ class PlayerActivity : AppCompatActivity() { private lateinit var videoZoomButton: ImageButton private lateinit var sectionsBottomSheet: BottomSheetDialog private lateinit var sectionsList: LinearLayout + private lateinit var exportBookmarksButton: ImageButton @@ -239,7 +240,7 @@ class PlayerActivity : AppCompatActivity() { val sectionItem = TextView(this).apply { text = title setPadding(16, 16, 16, 16) - setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) +setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_inverseSurface)) setOnClickListener { mediaController?.jumpToTimestamp(timestamp) sectionsBottomSheet.dismiss() @@ -278,7 +279,16 @@ class PlayerActivity : AppCompatActivity() { private fun setupSectionsBottomSheet() { val view = layoutInflater.inflate(R.layout.bottom_sheet_sections, null) sectionsList = view.findViewById(R.id.sections_list) + exportBookmarksButton = view.findViewById(R.id.btn_export_bookmarks) + exportBookmarksButton.setOnClickListener { + val bookmarksJsonArray = JSONArray(bookmarks) + val bookmarksJsonString = bookmarksJsonArray.toString() + val fileName = "bookmarks_${System.currentTimeMillis()}.json" + val file = File(getExternalFilesDir(null), fileName) + file.writeText(bookmarksJsonString) + Toast.makeText(this, "Bookmarks exported to $fileName", Toast.LENGTH_SHORT).show() + } sectionsBottomSheet = BottomSheetDialog(this).apply { setContentView(view) } @@ -299,137 +309,129 @@ class PlayerActivity : AppCompatActivity() { } override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - prettyPrintIntent() - - AppCompatDelegate.setDefaultNightMode( - when (applicationPreferences.themeConfig) { - ThemeConfig.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM - ThemeConfig.OFF -> AppCompatDelegate.MODE_NIGHT_NO - ThemeConfig.ON -> AppCompatDelegate.MODE_NIGHT_YES - }, - ) + super.onCreate(savedInstanceState) + prettyPrintIntent() + + AppCompatDelegate.setDefaultNightMode( + when (applicationPreferences.themeConfig) { + ThemeConfig.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + ThemeConfig.OFF -> AppCompatDelegate.MODE_NIGHT_NO + ThemeConfig.ON -> AppCompatDelegate.MODE_NIGHT_YES + }, + ) + + if (applicationPreferences.useDynamicColors) { + DynamicColors.applyToActivityIfAvailable(this) + } - if (applicationPreferences.useDynamicColors) { - DynamicColors.applyToActivityIfAvailable(this) - } + // The window is always allowed to extend into the DisplayCutout areas on the short edges of the screen + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES + } - // The window is always allowed to extend into the DisplayCutout areas on the short edges of the screen - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - window.attributes.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES - } + WindowCompat.setDecorFitsSystemWindows(window, false) - WindowCompat.setDecorFitsSystemWindows(window, false) - - binding = ActivityPlayerBinding.inflate(layoutInflater) - setContentView(binding.root) - - // Initializing views - audioTrackButton = binding.playerView.findViewById(R.id.btn_audio_track) - backButton = binding.playerView.findViewById(R.id.back_button) - exoContentFrameLayout = binding.playerView.findViewById(R.id.exo_content_frame) - lockControlsButton = binding.playerView.findViewById(R.id.btn_lock_controls) - playbackSpeedButton = binding.playerView.findViewById(R.id.btn_playback_speed) - playerLockControls = binding.playerView.findViewById(R.id.player_lock_controls) - playerUnlockControls = binding.playerView.findViewById(R.id.player_unlock_controls) - playerCenterControls = binding.playerView.findViewById(R.id.player_center_controls) - screenRotateButton = binding.playerView.findViewById(R.id.screen_rotate) - pipButton = binding.playerView.findViewById(R.id.btn_pip) - seekBar = binding.playerView.findViewById(R.id.exo_progress) - subtitleTrackButton = binding.playerView.findViewById(R.id.btn_subtitle_track) - unlockControlsButton = binding.playerView.findViewById(R.id.btn_unlock_controls) - videoTitleTextView = binding.playerView.findViewById(R.id.video_name) - videoZoomButton = binding.playerView.findViewById(R.id.btn_video_zoom) - val exportBookmarksButton: ImageButton = findViewById(R.id.btn_export_bookmarks) + binding = ActivityPlayerBinding.inflate(layoutInflater) + setContentView(binding.root) - exportBookmarksButton.setOnClickListener { - val bookmarksJsonArray = JSONArray(bookmarks) - val bookmarksJsonString = bookmarksJsonArray.toString() - val fileName = "bookmarks_${System.currentTimeMillis()}.json" - val file = File(getExternalFilesDir(null), fileName) - file.writeText(bookmarksJsonString) - Toast.makeText(this, "Bookmarks exported to $fileName", Toast.LENGTH_SHORT).show() - } + // Initializing views + audioTrackButton = binding.playerView.findViewById(R.id.btn_audio_track) + backButton = binding.playerView.findViewById(R.id.back_button) + exoContentFrameLayout = binding.playerView.findViewById(R.id.exo_content_frame) + lockControlsButton = binding.playerView.findViewById(R.id.btn_lock_controls) + playbackSpeedButton = binding.playerView.findViewById(R.id.btn_playback_speed) + playerLockControls = binding.playerView.findViewById(R.id.player_lock_controls) + playerUnlockControls = binding.playerView.findViewById(R.id.player_unlock_controls) + playerCenterControls = binding.playerView.findViewById(R.id.player_center_controls) + screenRotateButton = binding.playerView.findViewById(R.id.screen_rotate) + pipButton = binding.playerView.findViewById(R.id.btn_pip) + seekBar = binding.playerView.findViewById(R.id.exo_progress) + subtitleTrackButton = binding.playerView.findViewById(R.id.btn_subtitle_track) + unlockControlsButton = binding.playerView.findViewById(R.id.btn_unlock_controls) + videoTitleTextView = binding.playerView.findViewById(R.id.video_name) + videoZoomButton = binding.playerView.findViewById(R.id.btn_video_zoom) - // Initialize my views - btnShowSections = findViewById(R.id.btn_show_sections) - // Set up sections bottom sheet - setupSectionsBottomSheet() - // Set up button click listener - btnShowSections.setOnClickListener { - if (!isJsonFileLoaded && bookmarks.isEmpty()) { - filePickerLauncher.launch(arrayOf("application/json")) - } else { - sectionsBottomSheet.show() - } - } + // Initialize my views + btnShowSections = findViewById(R.id.btn_show_sections) - // Set up long click listener to add bookmarks - btnShowSections.setOnLongClickListener { - val currentPosition = mediaController?.currentPosition ?: return@setOnLongClickListener true - val bookmarkTitle = "Bookmark at ${Utils.formatDurationMillis(currentPosition)}" - val bookmark = JSONObject().apply { - put("title", bookmarkTitle) - put("timestamp", currentPosition) - } - bookmarks.add(bookmark) - Toast.makeText(this, "Bookmark added", Toast.LENGTH_SHORT).show() - populateSectionsList() - true - } + // Set up sections bottom sheet + setupSectionsBottomSheet() - playInBackgroundButton = binding.playerView.findViewById(R.id.btn_background) - extraControls = binding.playerView.findViewById(R.id.extra_controls) - - if (playerPreferences.controlButtonsPosition == ControlButtonsPosition.RIGHT) { - extraControls.gravity = Gravity.END + // Set up button click listener + btnShowSections.setOnClickListener { + if (!isJsonFileLoaded && bookmarks.isEmpty()) { + filePickerLauncher.launch(arrayOf("application/json")) + } else { + sectionsBottomSheet.show() } + } - if (!isPipSupported) { - pipButton.visibility = View.GONE + // Set up long click listener to add bookmarks + btnShowSections.setOnLongClickListener { + val currentPosition = mediaController?.currentPosition ?: return@setOnLongClickListener true + val bookmarkTitle = "Bookmark at ${Utils.formatDurationMillis(currentPosition)}" + val bookmark = JSONObject().apply { + put("title", bookmarkTitle) + put("timestamp", currentPosition) } + bookmarks.add(bookmark) + Toast.makeText(this, "Bookmark added", Toast.LENGTH_SHORT).show() + populateSectionsList() + true + } - seekBar.addListener( - object : TimeBar.OnScrubListener { - override fun onScrubStart(timeBar: TimeBar, position: Long) { - // Implementation - } + playInBackgroundButton = binding.playerView.findViewById(R.id.btn_background) + extraControls = binding.playerView.findViewById(R.id.extra_controls) - override fun onScrubMove(timeBar: TimeBar, position: Long) { - // Implementation - } + if (playerPreferences.controlButtonsPosition == ControlButtonsPosition.RIGHT) { + extraControls.gravity = Gravity.END + } - override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { - // Implementation - } - }, - ) + if (!isPipSupported) { + pipButton.visibility = View.GONE + } - volumeManager = VolumeManager(audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager) - brightnessManager = BrightnessManager(activity = this) - playerGestureHelper = PlayerGestureHelper( - viewModel = viewModel, - activity = this, - volumeManager = volumeManager, - brightnessManager = brightnessManager, - onScaleChanged = { scale -> - mediaController?.currentMediaItem?.mediaId?.let { - // Implementation - } - }, - ) + seekBar.addListener( + object : TimeBar.OnScrubListener { + override fun onScrubStart(timeBar: TimeBar, position: Long) { + // Implementation + } - playerApi = PlayerApi(this) + override fun onScrubMove(timeBar: TimeBar, position: Long) { + // Implementation + } - onBackPressedDispatcher.addCallback { - mediaController?.run { - clearMediaItems() - stop() + override fun onScrubStop(timeBar: TimeBar, position: Long, canceled: Boolean) { + // Implementation + } + }, + ) + + volumeManager = VolumeManager(audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager) + brightnessManager = BrightnessManager(activity = this) + playerGestureHelper = PlayerGestureHelper( + viewModel = viewModel, + activity = this, + volumeManager = volumeManager, + brightnessManager = brightnessManager, + onScaleChanged = { scale -> + mediaController?.currentMediaItem?.mediaId?.let { + // Implementation } + }, + ) + + playerApi = PlayerApi(this) + + onBackPressedDispatcher.addCallback { + mediaController?.run { + clearMediaItems() + stop() } } +} override fun onStart() { super.onStart() @@ -631,6 +633,8 @@ class PlayerActivity : AppCompatActivity() { ).show(supportFragmentManager, "TrackSelectionDialog") } + + subtitleTrackButton.setOnClickListener { TrackSelectionDialogFragment( type = C.TRACK_TYPE_TEXT, From 7c9c6ea1c3dc2b61f5563d513c804c8b9dbf7b36 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:20:45 +0200 Subject: [PATCH 16/18] Respect theme changes --- feature/player/src/main/res/drawable/ic_export.xml | 2 +- feature/player/src/main/res/layout/bottom_sheet_sections.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/player/src/main/res/drawable/ic_export.xml b/feature/player/src/main/res/drawable/ic_export.xml index 509bc6ffe..81edf577c 100644 --- a/feature/player/src/main/res/drawable/ic_export.xml +++ b/feature/player/src/main/res/drawable/ic_export.xml @@ -4,6 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> \ No newline at end of file diff --git a/feature/player/src/main/res/layout/bottom_sheet_sections.xml b/feature/player/src/main/res/layout/bottom_sheet_sections.xml index e8c46d408..faf012d2a 100644 --- a/feature/player/src/main/res/layout/bottom_sheet_sections.xml +++ b/feature/player/src/main/res/layout/bottom_sheet_sections.xml @@ -29,7 +29,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/transparent_circle_background" - android:contentDescription="Export Bookmarks" + android:contentDescription="Export bookmarks" android:padding="12dp" android:src="@drawable/ic_export" /> From 0a75e665d322f7402a7da3be98b58140371bd2dc Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:21:13 +0200 Subject: [PATCH 17/18] Update PlayerActivity.kt --- .../anilbeesetti/nextplayer/feature/player/PlayerActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index e00997c72..8aeeb7747 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -240,7 +240,6 @@ class PlayerActivity : AppCompatActivity() { val sectionItem = TextView(this).apply { text = title setPadding(16, 16, 16, 16) -setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_inverseSurface)) setOnClickListener { mediaController?.jumpToTimestamp(timestamp) sectionsBottomSheet.dismiss() @@ -257,7 +256,7 @@ setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextpl val sectionItem = TextView(this).apply { text = title setPadding(16, 16, 16, 16) - setTextColor(ContextCompat.getColor(this@PlayerActivity, dev.anilbeesetti.nextplayer.core.ui.R.color.md_theme_dark_tertiaryContainer)) + setOnClickListener { mediaController?.jumpToTimestamp(timestamp) sectionsBottomSheet.dismiss() From 2b856f846eaad2636fe4adb6d0d99d999c924bf0 Mon Sep 17 00:00:00 2001 From: Mazen Tamer Salah Date: Sun, 8 Dec 2024 12:41:11 +0200 Subject: [PATCH 18/18] naming refactoring --- .../feature/player/PlayerActivity.kt | 47 +++++++++++++------ .../main/res/layout/bottom_sheet_sections.xml | 4 +- .../player/src/main/res/values/strings.xml | 4 +- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt index 8aeeb7747..4c709f6ff 100644 --- a/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt +++ b/feature/player/src/main/java/dev/anilbeesetti/nextplayer/feature/player/PlayerActivity.kt @@ -34,6 +34,7 @@ import android.widget.TextView import android.widget.Toast import androidx.activity.addCallback +import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts.OpenDocument import androidx.activity.viewModels @@ -275,23 +276,39 @@ class PlayerActivity : AppCompatActivity() { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE) } - private fun setupSectionsBottomSheet() { - val view = layoutInflater.inflate(R.layout.bottom_sheet_sections, null) - sectionsList = view.findViewById(R.id.sections_list) - exportBookmarksButton = view.findViewById(R.id.btn_export_bookmarks) - - exportBookmarksButton.setOnClickListener { + private val createDocumentLauncher = registerForActivityResult(ActivityResultContracts.CreateDocument("application/json")) { uri -> + uri?.let { + try { val bookmarksJsonArray = JSONArray(bookmarks) val bookmarksJsonString = bookmarksJsonArray.toString() - val fileName = "bookmarks_${System.currentTimeMillis()}.json" - val file = File(getExternalFilesDir(null), fileName) - file.writeText(bookmarksJsonString) - Toast.makeText(this, "Bookmarks exported to $fileName", Toast.LENGTH_SHORT).show() - } - sectionsBottomSheet = BottomSheetDialog(this).apply { - setContentView(view) + contentResolver.openOutputStream(it)?.use { outputStream -> + outputStream.write(bookmarksJsonString.toByteArray()) + } + Toast.makeText(this, "Bookmarks exported successfully", Toast.LENGTH_SHORT).show() + } catch (e: Exception) { + e.printStackTrace() + Timber.e(e, "Failed to export JSON file: ${e.message}") + Toast.makeText(this, "Failed to export JSON file: ${e.message}", Toast.LENGTH_LONG).show() } + } ?: run { + Timber.e("Document creation returned a null URI") + Toast.makeText(this, "Export cancelled", Toast.LENGTH_SHORT).show() } +} + +private fun setupSectionsBottomSheet() { + val view = layoutInflater.inflate(R.layout.bottom_sheet_sections, null) + sectionsList = view.findViewById(R.id.sections_list) + exportBookmarksButton = view.findViewById(R.id.btn_export_bookmarks) + + exportBookmarksButton.setOnClickListener { + createDocumentLauncher.launch("${mediaController?.currentMediaItem?.mediaMetadata?.title}.json") + } + sectionsBottomSheet = BottomSheetDialog(this).apply { + setContentView(view) + } +} + private val isPipEnabled: Boolean get() { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -370,13 +387,13 @@ class PlayerActivity : AppCompatActivity() { // Set up long click listener to add bookmarks btnShowSections.setOnLongClickListener { val currentPosition = mediaController?.currentPosition ?: return@setOnLongClickListener true - val bookmarkTitle = "Bookmark at ${Utils.formatDurationMillis(currentPosition)}" + val bookmarkTitle = "${Utils.formatDurationMillis(currentPosition)}" val bookmark = JSONObject().apply { put("title", bookmarkTitle) put("timestamp", currentPosition) } bookmarks.add(bookmark) - Toast.makeText(this, "Bookmark added", Toast.LENGTH_SHORT).show() + Toast.makeText(this, "Section mark added", Toast.LENGTH_SHORT).show() populateSectionsList() true } diff --git a/feature/player/src/main/res/layout/bottom_sheet_sections.xml b/feature/player/src/main/res/layout/bottom_sheet_sections.xml index faf012d2a..b0269382c 100644 --- a/feature/player/src/main/res/layout/bottom_sheet_sections.xml +++ b/feature/player/src/main/res/layout/bottom_sheet_sections.xml @@ -19,7 +19,7 @@ @@ -29,7 +29,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/transparent_circle_background" - android:contentDescription="Export bookmarks" + android:contentDescription="@string/export_bookmarks" android:padding="12dp" android:src="@drawable/ic_export" /> diff --git a/feature/player/src/main/res/values/strings.xml b/feature/player/src/main/res/values/strings.xml index 3c1dd836d..159fe14a1 100644 --- a/feature/player/src/main/res/values/strings.xml +++ b/feature/player/src/main/res/values/strings.xml @@ -1,5 +1,5 @@ - Export Bookmarks - Sections + Export Sections + Chapters \ No newline at end of file