[Lyrics] Added lyrics editing on A11+ devices
This commit is contained in:
parent
39a2c45081
commit
af4347c4ff
6 changed files with 147 additions and 14 deletions
|
@ -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)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
@ -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,7 +255,24 @@ 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 {
|
||||||
|
if (VersionUtils.hasR()) {
|
||||||
|
cacheFile = TagWriter.writeTagsToFilesR(
|
||||||
|
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(
|
TagWriter.writeTagsToFiles(
|
||||||
requireContext(), AudioTagInfo(
|
requireContext(), AudioTagInfo(
|
||||||
listOf(song.data), fieldKeyValueMap, null
|
listOf(song.data), fieldKeyValueMap, null
|
||||||
|
@ -221,6 +280,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
positiveButton(res = R.string.save) {
|
positiveButton(res = R.string.save) {
|
||||||
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
|
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
|
||||||
}
|
}
|
||||||
|
@ -240,8 +300,34 @@ 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 ->
|
||||||
|
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())
|
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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue