Fixed tag editing for Android 11+ devices

main
Prathamesh More 2021-11-22 15:31:33 +05:30
parent 4eb2f68da5
commit 8e64f117f9
8 changed files with 323 additions and 181 deletions

View File

@ -21,28 +21,36 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.provider.MediaStore
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.animation.OvershootInterpolator import android.view.animation.OvershootInterpolator
import android.widget.ImageView import android.widget.ImageView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.ATHUtil import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.appthemehelper.util.TintHelper import code.name.monkey.appthemehelper.util.TintHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.drawable import code.name.monkey.retromusic.R.drawable
import code.name.monkey.retromusic.activities.base.AbsBaseActivity import code.name.monkey.retromusic.activities.base.AbsBaseActivity
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.accentColor import code.name.monkey.retromusic.extensions.accentColor
import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.model.LoadingInfo import code.name.monkey.retromusic.model.AudioTagInfo
import code.name.monkey.retromusic.repository.Repository import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
import code.name.monkey.retromusic.util.SAFUtil import code.name.monkey.retromusic.util.SAFUtil
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jaudiotagger.audio.AudioFile import org.jaudiotagger.audio.AudioFile
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
@ -66,9 +74,12 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
private var savedArtworkInfo: ArtworkInfo? = null private var savedArtworkInfo: ArtworkInfo? = null
private var _binding: VB? = null private var _binding: VB? = null
protected val binding: VB get() = _binding!! protected val binding: VB get() = _binding!!
private var cacheFiles = listOf<File>()
abstract val bindingInflater: (LayoutInflater) -> VB abstract val bindingInflater: (LayoutInflater) -> VB
private lateinit var launcher: ActivityResultLauncher<IntentSenderRequest>
protected abstract fun loadImageFromFile(selectedFile: Uri?) protected abstract fun loadImageFromFile(selectedFile: Uri?)
protected val show: AlertDialog protected val show: AlertDialog
@ -195,7 +206,6 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater) _binding = bindingInflater.invoke(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setStatusbarColorAuto()
setTaskDescriptionColorAuto() setTaskDescriptionColorAuto()
saveFab = findViewById(R.id.saveTags) saveFab = findViewById(R.id.saveTags)
@ -207,6 +217,11 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
finish() finish()
} }
setUpViews() setUpViews()
launcher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
writeToFiles(getSongUris(), cacheFiles)
}
}
} }
private fun setUpViews() { private fun setUpViews() {
@ -265,6 +280,8 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
protected abstract fun getSongPaths(): List<String> protected abstract fun getSongPaths(): List<String>
protected abstract fun getSongUris(): List<Uri>
protected fun searchWebFor(vararg keys: String) { protected fun searchWebFor(vararg keys: String) {
val stringBuilder = StringBuilder() val stringBuilder = StringBuilder()
for (key in keys) { for (key in keys) {
@ -336,23 +353,53 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
hideFab() hideFab()
println(fieldKeyValueMap) println(fieldKeyValueMap)
WriteTagsAsyncTask(this).execute( GlobalScope.launch {
LoadingInfo( if (VersionUtils.hasR()) {
songPaths, cacheFiles = TagWriter.writeTagsToFilesR(
fieldKeyValueMap, this@AbsTagEditorActivity, AudioTagInfo(
artworkInfo songPaths,
) fieldKeyValueMap,
) artworkInfo
)
)
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} else {
TagWriter.writeTagsToFiles(
this@AbsTagEditorActivity, AudioTagInfo(
songPaths,
fieldKeyValueMap,
artworkInfo
)
)
}
}
} }
private fun writeTags(paths: List<String>?) { private fun writeTags(paths: List<String>?) {
WriteTagsAsyncTask(this).execute( GlobalScope.launch {
LoadingInfo( if (VersionUtils.hasR()) {
paths, cacheFiles = TagWriter.writeTagsToFilesR(
savedTags, this@AbsTagEditorActivity, AudioTagInfo(
savedArtworkInfo paths,
) savedTags,
) savedArtworkInfo
)
)
val pendingIntent = MediaStore.createWriteRequest(contentResolver, getSongUris())
launcher.launch(IntentSenderRequest.Builder(pendingIntent).build())
} else {
TagWriter.writeTagsToFiles(
this@AbsTagEditorActivity, AudioTagInfo(
paths,
savedTags,
savedArtworkInfo
)
)
}
}
} }
@ -391,9 +438,30 @@ abstract class AbsTagEditorActivity<VB : ViewBinding> : AbsBaseActivity() {
} }
} }
private fun writeToFiles(songUris: List<Uri>, cacheFiles: List<File>) {
if (cacheFiles.size == songUris.size) {
for (i in cacheFiles.indices) {
contentResolver.openOutputStream(songUris[i])?.use { output ->
cacheFiles[i].inputStream().use { input ->
input.copyTo(output)
}
}
}
}
lifecycleScope.launch {
TagWriter.scan(this@AbsTagEditorActivity, getSongPaths())
}
}
override fun onDestroy() {
super.onDestroy()
// Delete Cache Files
cacheFiles.forEach { file ->
file.delete()
}
}
companion object { companion object {
const val EXTRA_ID = "extra_id" const val EXTRA_ID = "extra_id"
const val EXTRA_PALETTE = "extra_palette" const val EXTRA_PALETTE = "extra_palette"
private val TAG = AbsTagEditorActivity::class.java.simpleName private val TAG = AbsTagEditorActivity::class.java.simpleName

View File

@ -38,6 +38,7 @@ import code.name.monkey.retromusic.glide.palette.BitmapPaletteWrapper
import code.name.monkey.retromusic.model.ArtworkInfo import code.name.monkey.retromusic.model.ArtworkInfo
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.ImageUtil import code.name.monkey.retromusic.util.ImageUtil
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette import code.name.monkey.retromusic.util.RetroColorUtil.generatePalette
import code.name.monkey.retromusic.util.RetroColorUtil.getColor import code.name.monkey.retromusic.util.RetroColorUtil.getColor
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
@ -193,6 +194,11 @@ class AlbumTagEditorActivity : AbsTagEditorActivity<ActivityAlbumTagEditorBindin
.map(Song::data) .map(Song::data)
} }
override fun getSongUris(): List<Uri> = repository.albumById(id).songs.map {
MusicUtil.getSongFileUri(it.id)
}
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
} }

View File

@ -27,6 +27,7 @@ import code.name.monkey.retromusic.databinding.ActivitySongTagEditorBinding
import code.name.monkey.retromusic.extensions.appHandleColor import code.name.monkey.retromusic.extensions.appHandleColor
import code.name.monkey.retromusic.extensions.setTint import code.name.monkey.retromusic.extensions.setTint
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.util.MusicUtil
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import java.util.* import java.util.*
@ -111,6 +112,8 @@ class SongTagEditorActivity : AbsTagEditorActivity<ActivitySongTagEditorBinding>
override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data) override fun getSongPaths(): List<String> = listOf(songRepository.song(id).data)
override fun getSongUris(): List<Uri> = listOf(MusicUtil.getSongFileUri(id))
override fun loadImageFromFile(selectedFile: Uri?) { override fun loadImageFromFile(selectedFile: Uri?) {
} }

View File

@ -0,0 +1,203 @@
package code.name.monkey.retromusic.activities.tageditor
import android.app.Activity
import android.content.Context
import android.graphics.Bitmap
import android.media.MediaScannerConnection
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener
import code.name.monkey.retromusic.model.AudioTagInfo
import code.name.monkey.retromusic.util.MusicUtil.createAlbumArtFile
import code.name.monkey.retromusic.util.MusicUtil.deleteAlbumArt
import code.name.monkey.retromusic.util.MusicUtil.insertAlbumArt
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jaudiotagger.audio.AudioFileIO
import org.jaudiotagger.audio.exceptions.CannotReadException
import org.jaudiotagger.audio.exceptions.CannotWriteException
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException
import org.jaudiotagger.tag.TagException
import org.jaudiotagger.tag.images.Artwork
import org.jaudiotagger.tag.images.ArtworkFactory
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
class TagWriter {
companion object {
suspend fun scan(context: Context, toBeScanned: List<String?>?) {
if (toBeScanned == null || toBeScanned.isEmpty()) {
Log.i("scan", "scan: Empty")
Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show()
return
}
MediaScannerConnection.scanFile(
context,
toBeScanned.toTypedArray(),
null,
withContext(Dispatchers.Main) {
if (context is Activity) UpdateToastMediaScannerCompletionListener(
context, toBeScanned
) else null
}
)
}
suspend fun writeTagsToFiles(context: Context, info: AudioTagInfo) =
withContext(Dispatchers.IO) {
try {
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG,
0,
FileOutputStream(albumArtFile)
)
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val audioFile = AudioFileIO.read(File(filePath))
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
scan(context, info.filePaths)
} catch (e: Exception) {
e.printStackTrace()
scan(context, null)
}
}
@RequiresApi(Build.VERSION_CODES.R)
suspend fun writeTagsToFilesR(context: Context, info: AudioTagInfo) =
withContext(Dispatchers.IO) {
val cacheFiles = mutableListOf<File>()
try {
var artwork: Artwork? = null
var albumArtFile: File? = null
if (info.artworkInfo?.artwork != null) {
try {
albumArtFile = createAlbumArtFile(context).canonicalFile
info.artworkInfo.artwork.compress(
Bitmap.CompressFormat.PNG,
0,
FileOutputStream(albumArtFile)
)
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile)
} catch (e: IOException) {
e.printStackTrace()
}
}
var wroteArtwork = false
var deletedArtwork = false
for (filePath in info.filePaths!!) {
try {
val originFile = File(filePath)
val cacheFile = File(context.cacheDir, originFile.name)
cacheFiles.add(cacheFile)
originFile.inputStream().use { input ->
cacheFile.outputStream().use { output ->
input.copyTo(output)
}
}
val audioFile = AudioFileIO.read(cacheFile)
val tag = audioFile.tagOrCreateAndSetDefault
if (info.fieldKeyValueMap != null) {
for ((key, value) in info.fieldKeyValueMap) {
try {
tag.setField(key, value)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
if (info.artworkInfo != null) {
if (info.artworkInfo.artwork == null) {
tag.deleteArtworkField()
deletedArtwork = true
} else if (artwork != null) {
tag.deleteArtworkField()
tag.setField(artwork)
wroteArtwork = true
}
}
audioFile.commit()
} catch (e: CannotReadException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
} catch (e: CannotWriteException) {
e.printStackTrace()
} catch (e: TagException) {
e.printStackTrace()
} catch (e: ReadOnlyFileException) {
e.printStackTrace()
} catch (e: InvalidAudioFrameException) {
e.printStackTrace()
}
}
if (wroteArtwork) {
insertAlbumArt(context, info.artworkInfo!!.albumId, albumArtFile!!.path)
} else if (deletedArtwork) {
deleteAlbumArt(context, info.artworkInfo!!.albumId)
}
cacheFiles
} catch (e: Exception) {
e.printStackTrace()
listOf()
}
}
}
}

View File

@ -1,152 +0,0 @@
package code.name.monkey.retromusic.activities.tageditor;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.images.Artwork;
import org.jaudiotagger.tag.images.ArtworkFactory;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.misc.UpdateToastMediaScannerCompletionListener;
import code.name.monkey.retromusic.model.LoadingInfo;
import code.name.monkey.retromusic.util.MusicUtil;
public class WriteTagsAsyncTask extends DialogAsyncTask<LoadingInfo, Integer, List<String>> {
public WriteTagsAsyncTask(Context context) {
super(context);
}
@Override
protected List<String> doInBackground(LoadingInfo... params) {
try {
LoadingInfo info = params[0];
Artwork artwork = null;
File albumArtFile = null;
if (info.getArtworkInfo() != null && info.getArtworkInfo().getArtwork() != null) {
try {
albumArtFile = MusicUtil.INSTANCE.createAlbumArtFile().getCanonicalFile();
info.getArtworkInfo().getArtwork().compress(Bitmap.CompressFormat.PNG, 0, new FileOutputStream(albumArtFile));
artwork = ArtworkFactory.createArtworkFromFile(albumArtFile);
} catch (IOException e) {
e.printStackTrace();
}
}
int counter = 0;
boolean wroteArtwork = false;
boolean deletedArtwork = false;
for (String filePath : info.getFilePaths()) {
publishProgress(++counter, info.getFilePaths().size());
try {
AudioFile audioFile = AudioFileIO.read(new File(filePath));
Tag tag = audioFile.getTagOrCreateAndSetDefault();
if (info.getFieldKeyValueMap() != null) {
for (Map.Entry<FieldKey, String> entry : info.getFieldKeyValueMap().entrySet()) {
try {
tag.setField(entry.getKey(), entry.getValue());
} catch (Exception e) {
e.printStackTrace();
}
}
}
if (info.getArtworkInfo() != null) {
if (info.getArtworkInfo().getArtwork() == null) {
tag.deleteArtworkField();
deletedArtwork = true;
} else if (artwork != null) {
tag.deleteArtworkField();
tag.setField(artwork);
wroteArtwork = true;
}
}
audioFile.commit();
} catch (@NonNull CannotReadException | IOException | CannotWriteException | TagException | ReadOnlyFileException | InvalidAudioFrameException e) {
e.printStackTrace();
}
}
Context context = getContext();
if (context != null) {
if (wroteArtwork) {
MusicUtil.INSTANCE.
insertAlbumArt(context, info.getArtworkInfo().getAlbumId(), albumArtFile.getPath());
} else if (deletedArtwork) {
MusicUtil.INSTANCE.deleteAlbumArt(context, info.getArtworkInfo().getAlbumId());
}
}
return info.getFilePaths();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
protected void onPostExecute(List<String> toBeScanned) {
super.onPostExecute(toBeScanned);
scan(toBeScanned);
}
@Override
protected void onCancelled(List<String> toBeScanned) {
super.onCancelled(toBeScanned);
scan(toBeScanned);
}
private void scan(List<String> toBeScanned) {
Context context = getContext();
if (toBeScanned == null || toBeScanned.isEmpty()) {
Log.i("scan", "scan: Empty");
Toast.makeText(context, "Scan file from folder", Toast.LENGTH_SHORT).show();
return;
}
MediaScannerConnection.scanFile(context, toBeScanned.toArray(new String[0]), null, context instanceof Activity ? new UpdateToastMediaScannerCompletionListener((Activity) context, toBeScanned) : null);
}
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context)
.setTitle(R.string.saving_changes)
.setCancelable(false)
.setView(R.layout.loading)
.create();
}
@Override
protected void onProgressUpdate(@NonNull Dialog dialog, Integer... values) {
super.onProgressUpdate(dialog, values);
// ((MaterialDialog) dialog).setMaxProgress(values[1]);
// ((MaterialDialog) dialog).setProgress(values[0]);
}
}

View File

@ -14,19 +14,22 @@
*/ */
package code.name.monkey.retromusic.fragments.other package code.name.monkey.retromusic.fragments.other
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.text.InputType import android.text.InputType
import android.view.* import android.view.*
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.transition.Fade import androidx.transition.Fade
import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.adapter.FragmentStateAdapter
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity import code.name.monkey.retromusic.activities.MainActivity
import code.name.monkey.retromusic.activities.tageditor.WriteTagsAsyncTask import code.name.monkey.retromusic.activities.tageditor.TagWriter
import code.name.monkey.retromusic.databinding.FragmentLyricsBinding import code.name.monkey.retromusic.databinding.FragmentLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding import code.name.monkey.retromusic.databinding.FragmentNormalLyricsBinding
import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding import code.name.monkey.retromusic.databinding.FragmentSyncedLyricsBinding
@ -37,7 +40,7 @@ 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.LoadingInfo 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.LyricUtil
import code.name.monkey.retromusic.util.RetroUtil import code.name.monkey.retromusic.util.RetroUtil
@ -107,6 +110,9 @@ 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() {
@ -187,6 +193,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
} }
@SuppressLint("CheckResult")
private fun editNormalLyrics() { private fun editNormalLyrics() {
var content = "" var content = ""
val file = File(MusicPlayerRemote.currentSong.data) val file = File(MusicPlayerRemote.currentSong.data)
@ -205,11 +212,13 @@ 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()
WriteTagsAsyncTask(requireActivity()).execute( GlobalScope.launch {
LoadingInfo( TagWriter.writeTagsToFiles(
listOf(song.data), fieldKeyValueMap, null requireContext(), AudioTagInfo(
listOf(song.data), fieldKeyValueMap, null
)
) )
) }
} }
positiveButton(res = R.string.save) { positiveButton(res = R.string.save) {
(lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics() (lyricsSectionsAdapter.fragments[1].first as NormalLyrics).loadNormalLyrics()
@ -219,6 +228,7 @@ class LyricsFragment : AbsMusicServiceFragment(R.layout.fragment_lyrics) {
} }
@SuppressLint("CheckResult")
private fun editSyncedLyrics() { private fun editSyncedLyrics() {
var lrcFile: File? = null var lrcFile: File? = null
if (LyricUtil.isLrcOriginalFileExist(song.data)) { if (LyricUtil.isLrcOriginalFileExist(song.data)) {

View File

@ -2,7 +2,7 @@ package code.name.monkey.retromusic.model
import org.jaudiotagger.tag.FieldKey import org.jaudiotagger.tag.FieldKey
class LoadingInfo( class AudioTagInfo(
val filePaths: List<String>?, val filePaths: List<String>?,
val fieldKeyValueMap: Map<FieldKey, String>?, val fieldKeyValueMap: Map<FieldKey, String>?,
val artworkInfo: ArtworkInfo? val artworkInfo: ArtworkInfo?

View File

@ -17,6 +17,7 @@ import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.db.PlaylistEntity import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.SongEntity import code.name.monkey.retromusic.db.SongEntity
@ -75,15 +76,18 @@ object MusicUtil : KoinComponent {
return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1$string2" return if (string2.isNullOrEmpty()) if (string1.isNullOrEmpty()) "" else string1 else "$string1$string2"
} }
fun createAlbumArtFile(): File { fun createAlbumArtFile(context: Context): File {
return File( return File(
createAlbumArtDir(), createAlbumArtDir(context),
System.currentTimeMillis().toString() System.currentTimeMillis().toString()
) )
} }
private fun createAlbumArtDir(): File { private fun createAlbumArtDir(context: Context): File {
val albumArtDir = File(Environment.getExternalStorageDirectory(), "/albumthumbs/") val albumArtDir = File(
if (VersionUtils.hasR()) context.cacheDir else Environment.getExternalStorageDirectory(),
"/albumthumbs/"
)
if (!albumArtDir.exists()) { if (!albumArtDir.exists()) {
albumArtDir.mkdirs() albumArtDir.mkdirs()
try { try {
@ -520,7 +524,7 @@ object MusicUtil : KoinComponent {
} }
} }
@RequiresApi(Build.VERSION_CODES.Q) @RequiresApi(Build.VERSION_CODES.R)
fun deleteTracksQ(activity: Activity, songs: List<Song>) { fun deleteTracksQ(activity: Activity, songs: List<Song>) {
val pendingIntent = MediaStore.createDeleteRequest(activity.contentResolver, songs.map { val pendingIntent = MediaStore.createDeleteRequest(activity.contentResolver, songs.map {
getSongFileUri(it.id) getSongFileUri(it.id)