[Lyrics] Added lyrics editing on A11+ devices

This commit is contained in:
Prathamesh More 2022-01-18 20:48:18 +05:30
parent 39a2c45081
commit af4347c4ff
6 changed files with 147 additions and 14 deletions

View file

@ -21,9 +21,9 @@ fun Fragment.createNewFile(
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) registerForActivityResult(ActivityResultContracts.StartActivityForResult())
{ result: ActivityResult -> { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == Activity.RESULT_OK) {
val outputStream: OutputStream? = context?.contentResolver?.openOutputStream(result.data?.data!!)?.use { os->
context?.contentResolver?.openOutputStream(result.data?.data!!) write(os, result.data?.data)
write(outputStream, result.data?.data) }
} }
} }

View file

@ -0,0 +1,6 @@
package code.name.monkey.retromusic.extensions
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil
val Song.uri get() = MusicUtil.getSongFileUri(songId = id)

View file

@ -15,9 +15,16 @@
package code.name.monkey.retromusic.fragments.other package code.name.monkey.retromusic.fragments.other
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.text.InputType import android.text.InputType
import android.view.* import android.view.*
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -36,14 +43,14 @@ import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.extensions.surfaceColor import code.name.monkey.retromusic.extensions.surfaceColor
import code.name.monkey.retromusic.extensions.textColorSecondary import code.name.monkey.retromusic.extensions.textColorSecondary
import code.name.monkey.retromusic.extensions.uri
import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment import code.name.monkey.retromusic.fragments.base.AbsMusicServiceFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper import code.name.monkey.retromusic.helper.MusicProgressViewUpdateHelper
import code.name.monkey.retromusic.lyrics.LrcView import code.name.monkey.retromusic.lyrics.LrcView
import code.name.monkey.retromusic.model.AudioTagInfo import code.name.monkey.retromusic.model.AudioTagInfo
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.LyricUtil import code.name.monkey.retromusic.util.*
import code.name.monkey.retromusic.util.RetroUtil
import com.afollestad.materialdialogs.LayoutMode import com.afollestad.materialdialogs.LayoutMode
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.bottomsheets.BottomSheet import com.afollestad.materialdialogs.bottomsheets.BottomSheet
@ -55,6 +62,7 @@ import kotlinx.coroutines.*
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import java.io.File import java.io.File
import java.io.FileOutputStream
import java.util.* import java.util.*
class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) { class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
@ -95,6 +103,43 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
return transform return transform
} }
private lateinit var normalLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var newSyncedLyricsLauncher: ActivityResultLauncher<Intent>
private lateinit var editSyncedLyricsLauncher: ActivityResultLauncher<IntentSenderRequest>
private lateinit var cacheFile: File
private var syncedLyrics: String = ""
private lateinit var syncedFileUri: Uri
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Normal lyrics launcher
normalLyricsLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
FileUtils.copyFileToUri(requireContext(), cacheFile, song.uri)
}
}
newSyncedLyricsLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
context?.contentResolver?.openOutputStream(result.data?.data!!)?.use {
it.write(syncedLyrics.toByteArray())
}
}
}
editSyncedLyricsLauncher =
registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
requireContext().contentResolver.openOutputStream(syncedFileUri)?.use { os ->
(os as FileOutputStream).channel.truncate(0)
os.write(syncedLyrics.toByteArray())
os.flush()
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true) setHasOptionsMenu(true)
@ -111,9 +156,6 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
setupViews() setupViews()
setupToolbar() setupToolbar()
updateTitleSong() updateTitleSong()
if (VersionUtils.hasR()) {
binding.editButton.isVisible = false
}
} }
private fun setupViews() { private fun setupViews() {
@ -213,12 +255,30 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
) { _, input -> ) { _, input ->
val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java) val fieldKeyValueMap = EnumMap<FieldKey, String>(FieldKey::class.java)
fieldKeyValueMap[FieldKey.LYRICS] = input.toString() fieldKeyValueMap[FieldKey.LYRICS] = input.toString()
syncedLyrics = input.toString()
GlobalScope.launch { GlobalScope.launch {
TagWriter.writeTagsToFiles( if (VersionUtils.hasR()) {
requireContext(), AudioTagInfo( cacheFile = TagWriter.writeTagsToFilesR(
listOf(song.data), fieldKeyValueMap, null requireContext(), AudioTagInfo(
listOf(song.data), fieldKeyValueMap, null
)
)[0]
val pendingIntent =
MediaStore.createWriteRequest(
requireContext().contentResolver,
listOf(song.uri)
)
normalLyricsLauncher.launch(
IntentSenderRequest.Builder(pendingIntent).build()
) )
) } else {
TagWriter.writeTagsToFiles(
requireContext(), AudioTagInfo(
listOf(song.data), fieldKeyValueMap, null
)
)
}
} }
} }
positiveButton(res = R.string.save) { positiveButton(res = R.string.save) {
@ -240,7 +300,33 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
prefill = content, prefill = content,
inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT inputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE or InputType.TYPE_CLASS_TEXT
) { _, input -> ) { _, input ->
LyricUtil.writeLrc(song, input.toString()) if (VersionUtils.hasR()) {
syncedLyrics = input.toString()
val lrcFile = LyricUtil.getSyncedLyricsFile(song)
if (lrcFile?.exists() == true) {
syncedFileUri =
UriUtil.getUriFromPath(requireContext(), lrcFile.absolutePath)
val pendingIntent =
MediaStore.createWriteRequest(
requireContext().contentResolver,
listOf(syncedFileUri)
)
editSyncedLyricsLauncher.launch(
IntentSenderRequest.Builder(pendingIntent).build()
)
} else {
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
intent.putExtra(
Intent.EXTRA_TITLE,
LyricUtil.getLrcOriginalPath(File(song.data).name)
)
newSyncedLyricsLauncher.launch(intent)
}
} else {
LyricUtil.writeLrc(song, input.toString())
}
} }
positiveButton(res = R.string.save) { positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics() (lyricsSectionsAdapter.fragments[0].first as SyncedLyrics).loadLRCLyrics()

View file

@ -0,0 +1,16 @@
package code.name.monkey.retromusic.util
import android.content.Context
import android.net.Uri
import java.io.File
object FileUtils {
fun copyFileToUri(context: Context, fromFile: File, toUri: Uri) {
context.contentResolver.openOutputStream(toUri)
?.use { output ->
fromFile.inputStream().use { input ->
input.copyTo(output)
}
}
}
}

View file

@ -118,7 +118,7 @@ object LyricUtil {
return "$lrcRootPath$title - $artist.lrc" return "$lrcRootPath$title - $artist.lrc"
} }
private fun getLrcOriginalPath(filePath: String): String { fun getLrcOriginalPath(filePath: String): String {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc") return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc")
} }

View file

@ -0,0 +1,25 @@
package code.name.monkey.retromusic.util
import android.content.ContentUris
import android.content.Context
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.RequiresApi
object UriUtil {
@RequiresApi(Build.VERSION_CODES.Q)
fun getUriFromPath(context: Context, path: String): Uri {
val uri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
val proj = arrayOf(MediaStore.Files.FileColumns._ID)
context.contentResolver.query(
uri, proj, MediaStore.Files.FileColumns.DATA + "=?", arrayOf(path), null
)?.use { cursor ->
if (cursor.count != 0) {
cursor.moveToFirst()
return ContentUris.withAppendedId(uri, cursor.getLong(0))
}
}
return Uri.EMPTY
}
}