Code refactor
This commit is contained in:
parent
6d0898a49a
commit
5ebeb9c587
20 changed files with 199 additions and 243 deletions
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue