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)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_wrong_credentials)
.setPositiveButton(R.string.ok, null)
.setPositiveButton(android.R.string.ok, null)
.show()
RESULT_INVALID_TOKEN -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.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)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_issues_not_available)
.setPositiveButton(R.string.ok, null)
.setPositiveButton(android.R.string.ok, null)
else -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(R.string.ok) { _, _ -> tryToFinishActivity() }
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
}
}

View File

@ -18,4 +18,7 @@ interface PlayCountDao {
@Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC")
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())
.title(R.string.md_error_label)
.content(R.string.md_storage_perm_error)
.positiveText(R.string.ok)
.positiveText(android.R.string.ok)
.build();
}
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
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.core.text.HtmlCompat
import androidx.fragment.app.DialogFragment
import code.name.monkey.retromusic.EXTRA_SONG
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.extraNotNull
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.model.Song
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() {
@JvmField
var currentSong: Song? = null
private val libraryViewModel by sharedViewModel<LibraryViewModel>()
@JvmField
var songsToRemove: List<Song>? = null
companion object {
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 {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
val pair = if (songs.size > 1) {
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),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
@ -62,63 +57,15 @@ class DeleteSongsDialog : DialogFragment() {
return materialDialog(pair.first)
.setMessage(pair.second)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel, null)
.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()
}
songsToRemove = songs
deleteSongsAsyncTask = DeleteSongsAsyncTask(this@DeleteSongsDialog)
deleteSongsAsyncTask?.execute(DeleteSongsAsyncTask.LoadingInfo(songs, null))
MusicUtil.deleteTracks(requireActivity(), songs)
libraryViewModel.deleteTracks(songs)
}
.create()
.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)
.setPositiveButton(R.string.ok, null)
.setPositiveButton(android.R.string.ok, null)
.setView(dialogView)
.create()
.colorButtons()

View File

@ -30,12 +30,16 @@ class LibraryViewModel(
val paletteColorLiveData: LiveData<Int> = paletteColor
init {
fetchHomeSections()
}
private fun loadLibraryContent() = viewModelScope.launch(IO) {
fetchHomeSections()
fetchSongs()
fetchAlbums()
fetchArtists()
fetchGenres()
fetchHomeSections()
fetchPlaylists()
}
@ -70,7 +74,6 @@ class LibraryViewModel(
}
fun getHome(): LiveData<List<Home>> {
fetchHomeSections()
return home
}
@ -132,11 +135,10 @@ class LibraryViewModel(
}
override fun onMediaStoreChanged() {
loadLibraryContent()
println("onMediaStoreChanged")
loadLibraryContent()
}
override fun onServiceConnected() {
println("onServiceConnected")
}
@ -204,6 +206,30 @@ class LibraryViewModel(
suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long =
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 {

View File

@ -4,15 +4,19 @@ import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.core.text.HtmlCompat
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.NavigationUI
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.common.ATHToolbarActivity.getToolbarBackgroundColor
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
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.fragments.base.AbsMainActivityFragment
import kotlinx.android.synthetic.main.fragment_library.*
import java.lang.String
class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
@ -31,6 +35,17 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
)
}
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() {
@ -61,7 +76,10 @@ class LibraryFragment : AbsMainActivityFragment(R.layout.fragment_library) {
null,
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(
childFragmentManager,
"ShowCreatePlaylistDialog"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,11 +4,13 @@ import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.Cursor
import android.net.Uri
import android.os.Environment
import android.provider.BaseColumns
import android.provider.MediaStore
import android.text.TextUtils
import android.util.Log
import android.widget.Toast
import androidx.core.content.FileProvider
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.lyrics.AbsSynchronizedLyrics
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.service.MusicService
import org.jaudiotagger.audio.AudioFileIO
@ -421,6 +424,78 @@ object MusicUtil : KoinComponent {
.show()
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)
.setTitle(R.string.dialog_title_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)
intent.data = Uri.parse("package:" + context.applicationContext.packageName)
context.startActivity(intent)

View File

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

View File

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

View File

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

View File

@ -883,6 +883,7 @@
<!-- TODO: Remove or change this placeholder text -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<string name="done">Done</string>
<string name="ok">Ok</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>