Code refactor

main
Hemanth S 2020-09-09 18:07:25 +05:30
parent 6d0898a49a
commit 5ebeb9c587
20 changed files with 199 additions and 243 deletions

View File

@ -280,21 +280,21 @@ open class BugReportActivity : AbsThemeActivity() {
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(context) RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed) .setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_wrong_credentials) .setMessage(R.string.bug_report_failed_wrong_credentials)
.setPositiveButton(R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.show() .show()
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context) RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed) .setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_invalid_token) .setMessage(R.string.bug_report_failed_invalid_token)
.setPositiveButton(R.string.ok, null).show() .setPositiveButton(android.R.string.ok, null).show()
RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(context) RESULT_ISSUES_NOT_ENABLED -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed) .setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_issues_not_available) .setMessage(R.string.bug_report_failed_issues_not_available)
.setPositiveButton(R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
else -> MaterialAlertDialogBuilder(context) else -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed) .setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown) .setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(R.string.ok) { _, _ -> tryToFinishActivity() } .setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() } .setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
} }
} }

View File

@ -18,4 +18,7 @@ interface PlayCountDao {
@Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC") @Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC")
fun playCountSongs(): List<PlayCountEntity> fun playCountSongs(): List<PlayCountEntity>
@Query("DELETE FROM SongEntity WHERE id =:songId")
fun deleteSong(songId: Int)
} }

View File

@ -76,7 +76,7 @@ public class BlacklistFolderChooserDialog extends DialogFragment implements Mate
return new MaterialDialog.Builder(requireActivity()) return new MaterialDialog.Builder(requireActivity())
.title(R.string.md_error_label) .title(R.string.md_error_label)
.content(R.string.md_storage_perm_error) .content(R.string.md_storage_perm_error)
.positiveText(R.string.ok) .positiveText(android.R.string.ok)
.build(); .build();
} }
if (savedInstanceState == null) { if (savedInstanceState == null) {

View File

@ -1,145 +0,0 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import code.name.monkey.retromusic.R;
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity;
import code.name.monkey.retromusic.misc.DialogAsyncTask;
import code.name.monkey.retromusic.model.Song;
import code.name.monkey.retromusic.util.SAFUtil;
/**
* Created by hemanths on 2019-07-31.
*/
public class DeleteSongsAsyncTask extends DialogAsyncTask<DeleteSongsAsyncTask.LoadingInfo, Integer, Void> {
private WeakReference<FragmentActivity> activityWeakReference;
private WeakReference<DeleteSongsDialog> dialogReference;
public DeleteSongsAsyncTask(@NonNull DeleteSongsDialog dialog) {
super(dialog.getActivity());
this.dialogReference = new WeakReference<>(dialog);
this.activityWeakReference = new WeakReference<>(dialog.getActivity());
}
@NonNull
@Override
protected Dialog createDialog(@NonNull Context context) {
return new MaterialAlertDialogBuilder(context,
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert)
.setTitle(R.string.deleting_songs)
.setView(R.layout.loading)
.setCancelable(false)
.create();
}
@Nullable
@Override
protected Void doInBackground(@NonNull LoadingInfo... loadingInfos) {
try {
LoadingInfo info = loadingInfos[0];
DeleteSongsDialog dialog = this.dialogReference.get();
FragmentActivity fragmentActivity = this.activityWeakReference.get();
if (dialog == null || fragmentActivity == null) {
return null;
}
if (!info.isIntent) {
if (!SAFUtil.isSAFRequiredForSongs(info.songs)) {
dialog.deleteSongs(info.songs, null);
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (SAFUtil.isSDCardAccessGranted(fragmentActivity)) {
dialog.deleteSongs(info.songs, null);
} else {
dialog.startActivityForResult(new Intent(fragmentActivity, SAFGuideActivity.class),
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE);
}
} else {
Log.i("Hmm", "doInBackground: kitkat delete songs");
}
}
} else {
switch (info.requestCode) {
case SAFUtil.REQUEST_SAF_PICK_TREE:
if (info.resultCode == Activity.RESULT_OK) {
SAFUtil.saveTreeUri(fragmentActivity, info.intent);
if (dialog.songsToRemove != null) {
dialog.deleteSongs(dialog.songsToRemove, null);
}
}
break;
case SAFUtil.REQUEST_SAF_PICK_FILE:
if (info.resultCode == Activity.RESULT_OK) {
dialog.deleteSongs(Collections.singletonList(dialog.currentSong),
Collections.singletonList(info.intent.getData()));
}
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static class LoadingInfo {
public Intent intent;
public boolean isIntent;
public int requestCode;
public int resultCode;
public List<Uri> safUris;
public List<Song> songs;
public LoadingInfo(List<Song> songs, List<Uri> safUris) {
this.isIntent = false;
this.songs = songs;
this.safUris = safUris;
}
public LoadingInfo(int requestCode, int resultCode, Intent intent) {
this.isIntent = true;
this.requestCode = requestCode;
this.resultCode = resultCode;
this.intent = intent;
}
}
}

View File

@ -1,50 +1,45 @@
/*
* Copyright (c) 2019 Hemanth Savarala.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*/
package code.name.monkey.retromusic.dialogs package code.name.monkey.retromusic.dialogs
import android.app.Dialog import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG import code.name.monkey.retromusic.EXTRA_SONG
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.saf.SAFGuideActivity
import code.name.monkey.retromusic.extensions.colorButtons import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.extraNotNull import code.name.monkey.retromusic.extensions.extraNotNull
import code.name.monkey.retromusic.extensions.materialDialog import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.SAFUtil import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class DeleteSongsDialog : DialogFragment() { class DeleteSongsDialog : DialogFragment() {
@JvmField private val libraryViewModel by sharedViewModel<LibraryViewModel>()
var currentSong: Song? = null
@JvmField companion object {
var songsToRemove: List<Song>? = null fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
private var deleteSongsAsyncTask: DeleteSongsAsyncTask? = null fun create(songs: List<Song>): DeleteSongsDialog {
val dialog = DeleteSongsDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
val pair = if (songs.size > 1) { val pair = if (songs.size > 1) {
Pair( Pair(
R.string.delete_songs_title, HtmlCompat.fromHtml( R.string.delete_songs_title,
HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size), String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY HtmlCompat.FROM_HTML_MODE_LEGACY
) )
@ -62,63 +57,15 @@ class DeleteSongsDialog : DialogFragment() {
return materialDialog(pair.first) return materialDialog(pair.first)
.setMessage(pair.second) .setMessage(pair.second)
.setCancelable(false) .setCancelable(false)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ -> .setPositiveButton(R.string.action_delete) { _, _ ->
if ((songs.size == 1) && MusicPlayerRemote.isPlaying(songs[0])) { if (songs.isNotEmpty() and (songs.size == 1) and MusicPlayerRemote.isPlaying(songs.first())) {
MusicPlayerRemote.playNextSong() MusicPlayerRemote.playNextSong()
} }
songsToRemove = songs MusicUtil.deleteTracks(requireActivity(), songs)
deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog) libraryViewModel.deleteTracks(songs)
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null))
} }
.create() .create()
.colorButtons() .colorButtons()
} }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
SAFGuideActivity.REQUEST_CODE_SAF_GUIDE -> {
SAFUtil.openTreePicker(this)
}
SAFUtil.REQUEST_SAF_PICK_TREE,
SAFUtil.REQUEST_SAF_PICK_FILE -> {
if (deleteSongsAsyncTask != null) {
deleteSongsAsyncTask?.cancel(true)
}
deleteSongsAsyncTask = DeleteSongsAsyncTask(this)
deleteSongsAsyncTask?.execute(
DeleteSongsAsyncTask.LoadingInfo(
requestCode,
resultCode,
data
)
)
}
}
}
fun deleteSongs(songs: List<Song>, safUris: List<Uri>?) {
MusicUtil.deleteTracks(requireActivity(), songs, safUris, Runnable {
dismiss()
})
}
companion object {
fun create(song: Song): DeleteSongsDialog {
val list = ArrayList<Song>()
list.add(song)
return create(list)
}
fun create(songs: List<Song>): DeleteSongsDialog {
val dialog = DeleteSongsDialog()
val args = Bundle()
args.putParcelableArrayList(EXTRA_SONG, ArrayList(songs))
dialog.arguments = args
return dialog
}
}
}

View File

@ -0,0 +1,24 @@
package code.name.monkey.retromusic.dialogs
import android.app.Dialog
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.extensions.colorButtons
import code.name.monkey.retromusic.extensions.materialDialog
import code.name.monkey.retromusic.fragments.LibraryViewModel
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
class ImportPlaylistDialog : DialogFragment() {
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return materialDialog(R.string.import_playlist)
.setMessage(R.string.import_playlist_message)
.setPositiveButton(R.string.import_label) { _, _ ->
libraryViewModel.importPlaylists()
}
.create()
.colorButtons()
}
}

View File

@ -144,7 +144,7 @@ class SongDetailDialog : DialogFragment() {
} }
} }
return materialDialog(R.string.action_details) return materialDialog(R.string.action_details)
.setPositiveButton(R.string.ok, null) .setPositiveButton(android.R.string.ok, null)
.setView(dialogView) .setView(dialogView)
.create() .create()
.colorButtons() .colorButtons()

View File

@ -30,12 +30,16 @@ class LibraryViewModel(
val paletteColorLiveData: LiveData<Int> = paletteColor val paletteColorLiveData: LiveData<Int> = paletteColor
init {
fetchHomeSections()
}
private fun loadLibraryContent() = viewModelScope.launch(IO) { private fun loadLibraryContent() = viewModelScope.launch(IO) {
fetchHomeSections()
fetchSongs() fetchSongs()
fetchAlbums() fetchAlbums()
fetchArtists() fetchArtists()
fetchGenres() fetchGenres()
fetchHomeSections()
fetchPlaylists() fetchPlaylists()
} }
@ -70,7 +74,6 @@ class LibraryViewModel(
} }
fun getHome(): LiveData<List<Home>> { fun getHome(): LiveData<List<Home>> {
fetchHomeSections()
return home return home
} }
@ -132,11 +135,10 @@ class LibraryViewModel(
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadLibraryContent()
println("onMediaStoreChanged") println("onMediaStoreChanged")
loadLibraryContent()
} }
override fun onServiceConnected() { override fun onServiceConnected() {
println("onServiceConnected") println("onServiceConnected")
} }
@ -204,6 +206,30 @@ class LibraryViewModel(
suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long =
repository.createPlaylist(playlistEntity) repository.createPlaylist(playlistEntity)
fun importPlaylists() = viewModelScope.launch(IO) {
val playlists = repository.fetchLegacyPlaylist()
playlists.forEach { playlist ->
val playlistEntity = repository.checkPlaylistExists(playlist.name).firstOrNull();
if (playlistEntity != null) {
val songEntities = playlist.getSongs().map {
it.toSongEntity(playlistEntity.playListId)
}
repository.insertSongs(songEntities)
} else {
val playListId = createPlaylist(PlaylistEntity(playlist.name))
val songEntities = playlist.getSongs().map {
it.toSongEntity(playListId.toInt())
}
repository.insertSongs(songEntities)
}
forceReload(Playlists)
}
}
fun deleteTracks(songs: List<Song>) = viewModelScope.launch(IO) {
repository.deleteSongs(songs)
}
} }
enum class ReloadType { enum class ReloadType {

View File

@ -4,15 +4,19 @@ import android.os.Bundle
import android.view.Menu import android.view.Menu
import android.view.MenuInflater import android.view.MenuInflater
import android.view.MenuItem import android.view.MenuItem
import androidx.core.text.HtmlCompat
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.NavigationUI import androidx.navigation.ui.NavigationUI
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog import code.name.monkey.retromusic.dialogs.CreatePlaylistDialog
import code.name.monkey.retromusic.dialogs.ImportPlaylistDialog
import code.name.monkey.retromusic.extensions.findNavController import code.name.monkey.retromusic.extensions.findNavController
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import kotlinx.android.synthetic.main.fragment_library.* import kotlinx.android.synthetic.main.fragment_library.*
import java.lang.String
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) { class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
@ -31,6 +35,17 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
) )
} }
setupNavigationController() setupNavigationController()
setupTitle()
}
private fun setupTitle() {
val color = ThemeStore.accentColor(requireContext())
val hexColor = String.format("#%06X", 0xFFFFFF and color)
val appName = HtmlCompat.fromHtml(
"Retro <span style='color:$hexColor';>Music</span>",
HtmlCompat.FROM_HTML_MODE_COMPACT
)
appNameText.text = appName
} }
private fun setupNavigationController() { private fun setupNavigationController() {
@ -61,7 +76,10 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
null, null,
navOptions navOptions
) )
R.id.action_import_playlist -> findNavController(R.id.fragment_container).navigate(R.id.action_import_playlist) R.id.action_import_playlist -> ImportPlaylistDialog().show(
childFragmentManager,
"ImportPlaylist"
)
R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show( R.id.action_add_to_playlist -> CreatePlaylistDialog.create(emptyList()).show(
childFragmentManager, childFragmentManager,
"ShowCreatePlaylistDialog" "ShowCreatePlaylistDialog"

View File

@ -20,7 +20,6 @@ class SongsFragment : AbsRecyclerViewCustomGridSizeFragment<SongAdapter, GridLay
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
libraryViewModel.getSongs().observe(viewLifecycleOwner, Observer { libraryViewModel.getSongs().observe(viewLifecycleOwner, Observer {
println(Thread.currentThread().name)
if (it.isNotEmpty()) if (it.isNotEmpty())
adapter?.swapDataSet(it) adapter?.swapDataSet(it)
else else

View File

@ -72,7 +72,7 @@ class LibraryPreferenceDialog : DialogFragment() {
categoryAdapter.categoryInfos = PreferenceUtil.defaultCategories categoryAdapter.categoryInfos = PreferenceUtil.defaultCategories
} }
.setNegativeButton(android.R.string.cancel, null) .setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.ok) { _, _ -> updateCategories(categoryAdapter.categoryInfos) } .setPositiveButton(android.R.string.ok) { _, _ -> updateCategories(categoryAdapter.categoryInfos) }
.setView(view) .setView(view)
.create() .create()
.colorButtons() .colorButtons()

View File

@ -96,6 +96,7 @@ interface Repository {
suspend fun playCountSongs(): List<PlayCountEntity> suspend fun playCountSongs(): List<PlayCountEntity>
suspend fun blackListPaths(): List<BlackListStoreEntity> suspend fun blackListPaths(): List<BlackListStoreEntity>
suspend fun lyrics(artist: String, title: String): Result<String> suspend fun lyrics(artist: String, title: String): Result<String>
suspend fun deleteSongs(songs: List<Song>)
} }
class RealRepository( class RealRepository(
@ -119,6 +120,8 @@ class RealRepository(
Error Error
} }
override suspend fun deleteSongs(songs: List<Song>) = roomRepository.deleteSongs(songs)
override suspend fun fetchAlbums(): List<Album> = albumRepository.albums() override suspend fun fetchAlbums(): List<Album> = albumRepository.albums()
override suspend fun albumByIdAsync(albumId: Int): Album = albumRepository.album(albumId) override suspend fun albumByIdAsync(albumId: Int): Album = albumRepository.album(albumId)

View File

@ -38,6 +38,7 @@ interface RoomRepository {
suspend fun clearBlacklist() suspend fun clearBlacklist()
suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity) suspend fun insertBlacklistPathAsync(blackListStoreEntity: BlackListStoreEntity)
suspend fun blackListPaths(): List<BlackListStoreEntity> suspend fun blackListPaths(): List<BlackListStoreEntity>
suspend fun deleteSongs(songs: List<Song>)
} }
class RealRoomRepository( class RealRoomRepository(
@ -153,6 +154,12 @@ class RealRoomRepository(
override suspend fun blackListPaths(): List<BlackListStoreEntity> = override suspend fun blackListPaths(): List<BlackListStoreEntity> =
blackListStoreDao.blackListPaths() blackListStoreDao.blackListPaths()
override suspend fun deleteSongs(songs: List<Song>) {
songs.forEach {
playCountDao.deleteSong(it.id)
}
}
override suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity) = override suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity) =
blackListStoreDao.deleteBlacklistPath(blackListStoreEntity) blackListStoreDao.deleteBlacklistPath(blackListStoreEntity)

View File

@ -155,7 +155,6 @@ class RealSongRepository(private val context: Context) : SongRepository {
val uri = if (VersionUtils.hasQ()) { val uri = if (VersionUtils.hasQ()) {
MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
} else { } else {
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI MediaStore.Audio.Media.EXTERNAL_CONTENT_URI

View File

@ -4,11 +4,13 @@ import android.content.ContentUris
import android.content.ContentValues import android.content.ContentValues
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.Environment import android.os.Environment
import android.provider.BaseColumns import android.provider.BaseColumns
import android.provider.MediaStore import android.provider.MediaStore
import android.text.TextUtils import android.text.TextUtils
import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -20,6 +22,7 @@ import code.name.monkey.retromusic.model.Playlist
import code.name.monkey.retromusic.model.Song import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics import code.name.monkey.retromusic.model.lyrics.AbsSynchronizedLyrics
import code.name.monkey.retromusic.repository.RealPlaylistRepository import code.name.monkey.retromusic.repository.RealPlaylistRepository
import code.name.monkey.retromusic.repository.RealSongRepository
import code.name.monkey.retromusic.repository.SongRepository import code.name.monkey.retromusic.repository.SongRepository
import code.name.monkey.retromusic.service.MusicService import code.name.monkey.retromusic.service.MusicService
import org.jaudiotagger.audio.AudioFileIO import org.jaudiotagger.audio.AudioFileIO
@ -421,6 +424,78 @@ object MusicUtil : KoinComponent {
.show() .show()
callback?.run() callback?.run()
} }
} }
} }
fun deleteTracks(context: Context, songs: List<Song>) {
val projection = arrayOf(
BaseColumns._ID, MediaStore.MediaColumns.DATA
)
val selection = StringBuilder()
selection.append(BaseColumns._ID + " IN (")
for (i in songs.indices) {
selection.append(songs[i].id)
if (i < songs.size - 1) {
selection.append(",")
}
}
selection.append(")")
var deletedCount = 0
try {
val cursor: Cursor? = context.contentResolver.query(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, projection, selection.toString(),
null, null
)
if (cursor != null) {
// Step 1: Remove selected tracks from the current playlist, as well
// as from the album art cache
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val id: Int = cursor.getInt(0)
val song: Song = RealSongRepository(context).song(id)
removeFromQueue(song)
cursor.moveToNext()
}
// Step 2: Remove files from card
cursor.moveToFirst()
while (!cursor.isAfterLast) {
val id: Int = cursor.getInt(0)
val name: String = cursor.getString(1)
try { // File.delete can throw a security exception
val f = File(name)
if (f.delete()) {
// Step 3: Remove selected track from the database
context.contentResolver.delete(
ContentUris.withAppendedId(
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
id.toLong()
), null, null
)
deletedCount++
} else {
// I'm not sure if we'd ever get here (deletion would
// have to fail, but no exception thrown)
Log.e("MusicUtils", "Failed to delete file $name")
}
cursor.moveToNext()
} catch (ex: SecurityException) {
cursor.moveToNext()
} catch (e: NullPointerException) {
Log.e("MusicUtils", "Failed to find file $name")
}
}
cursor.close()
}
Toast.makeText(
context,
context.getString(R.string.deleted_x_songs, deletedCount),
Toast.LENGTH_SHORT
).show()
} catch (ignored: SecurityException) {
}
}
} }

View File

@ -77,7 +77,7 @@ class RingtoneManager(val context: Context) {
return MaterialAlertDialogBuilder(context) return MaterialAlertDialogBuilder(context)
.setTitle(R.string.dialog_title_set_ringtone) .setTitle(R.string.dialog_title_set_ringtone)
.setMessage(R.string.dialog_message_set_ringtone) .setMessage(R.string.dialog_message_set_ringtone)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS) val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:" + context.applicationContext.packageName) intent.data = Uri.parse("package:" + context.applicationContext.packageName)
context.startActivity(intent) context.startActivity(intent)

View File

@ -31,7 +31,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/app_name" android:id="@+id/appNameText"
android:textAppearance="@style/TextViewHeadline6" android:textAppearance="@style/TextViewHeadline6"
android:textStyle="bold" /> android:textStyle="bold" />
</androidx.appcompat.widget.Toolbar> </androidx.appcompat.widget.Toolbar>

View File

@ -18,7 +18,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="2dp" android:layout_margin="2dp"
android:background="?rectSelector" android:background="?attr/rectSelector"
android:minHeight="64dp" android:minHeight="64dp"
android:padding="14dp" android:padding="14dp"
tools:ignore="UnusedAttribute"> tools:ignore="UnusedAttribute">

View File

@ -5,8 +5,7 @@
android:id="@+id/recentArtistContainer" android:id="@+id/recentArtistContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical" >
android:paddingBottom="12dp">
<LinearLayout <LinearLayout
android:id="@+id/clickable_area" android:id="@+id/clickable_area"
@ -14,7 +13,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:clickable="true" android:clickable="true"
android:focusable="true" android:focusable="true"
android:foreground="?attr/rectSelector" android:background="?attr/rectSelector"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"

View File

@ -883,6 +883,7 @@
<!-- TODO: Remove or change this placeholder text --> <!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string> <string name="hello_blank_fragment">Hello blank fragment</string>
<string name="done">Done</string> <string name="done">Done</string>
<string name="ok">Ok</string>
<string name="import_playlist">Import playlist</string> <string name="import_playlist">Import playlist</string>
<string name="import_playlist_message">It imports all playlists listed in the Android Media Store with songs, if the playlists already exists, the songs will get merged.</string>
<string name="import_label">Import</string>
</resources> </resources>