Merge branch 'room-playlist' of https://github.com/h4h13/RetroMusicPlayer into room-playlist

This commit is contained in:
Hemanth S 2020-08-24 22:00:49 +05:30
commit 3265580275
126 changed files with 1023 additions and 718 deletions

View file

@ -34,7 +34,7 @@ android {
}
signingConfigs {
release {
Properties properties = getProperties('/Users/h4h13/Documents/Github/retro.properties')
Properties properties = getProperties('/Users/apple/Documents/Github/retro.properties ')
storeFile file(getProperty(properties, 'storeFile'))
keyAlias getProperty(properties, 'keyAlias')
storePassword getProperty(properties, 'storePassword')
@ -129,6 +129,7 @@ dependencies {
implementation "com.afollestad.material-dialogs:core:$material_dialog_version"
implementation "com.afollestad.material-dialogs:commons:$material_dialog_version"
implementation 'com.afollestad:material-cab:0.1.12'
implementation 'com.afollestad:recyclical:1.1.1'
implementation 'com.github.bumptech.glide:glide:3.8.0'
implementation 'com.github.bumptech.glide:okhttp3-integration:1.5.0'
@ -180,5 +181,8 @@ dependencies {
implementation "androidx.room:room-runtime:$room_version"
kapt "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-ktx:$room_version"
debugImplementation 'com.amitshekhar.android:debug-db:1.0.6'
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font
android:font="@font/regular"
android:font="@font/google_sans_regular"
android:fontWeight="400" />
<font
android:font="@font/medium"
android:font="@font/google_sans_medium"
android:fontWeight="600" />
<font
android:font="@font/bold"
android:font="@font/google_sans_bold"
android:fontWeight="700" />
</font-family>

View file

@ -255,11 +255,9 @@
<meta-data
android:name="com.bumptech.glide.integration.okhttp3.OkHttpGlideModule"
android:value="GlideModule" />
<meta-data
android:name="com.android.vending.splits.required"
android:value="true" />
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
</application>
</manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 51 KiB

View file

@ -20,7 +20,6 @@ import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.VersionUtils
import code.name.monkey.retromusic.Constants.PRO_VERSION_PRODUCT_ID
import code.name.monkey.retromusic.appshortcuts.DynamicShortcutManager
import com.amitshekhar.DebugDB
import com.anjlab.android.iab.v3.BillingProcessor
import com.anjlab.android.iab.v3.TransactionDetails
import org.koin.android.ext.koin.androidContext
@ -33,7 +32,7 @@ class App : MultiDexApplication() {
override fun onCreate() {
super.onCreate()
instance = this
DebugDB.getAddressLog();
startKoin {
androidContext(this@App)
modules(appModules)

View file

@ -22,4 +22,7 @@ const val TOP_ARTISTS = 0
const val SUGGESTIONS = 5
const val FAVOURITES = 4
const val GENRES = 6
const val PLAYLISTS = 7
const val PLAYLISTS = 7
const val HISTORY_PLAYLIST = 8
const val LAST_ADDED_PLAYLIST = 9
const val TOP_PLAYED_PLAYLIST = 10

View file

@ -1,9 +1,12 @@
package code.name.monkey.retromusic
import code.name.monkey.retromusic.db.PlaylistDatabase
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.sqlite.db.SupportSQLiteDatabase
import code.name.monkey.retromusic.db.BlackListStoreEntity
import code.name.monkey.retromusic.db.PlaylistDao
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.repository.RealRoomPlaylistRepository
import code.name.monkey.retromusic.repository.RoomPlaylistRepository
import code.name.monkey.retromusic.db.RetroDatabase
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.albums.AlbumDetailsViewModel
import code.name.monkey.retromusic.fragments.artists.ArtistDetailsViewModel
@ -13,11 +16,42 @@ import code.name.monkey.retromusic.fragments.search.SearchViewModel
import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.network.networkModule
import code.name.monkey.retromusic.repository.*
import code.name.monkey.retromusic.util.FilePathUtil
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind
import org.koin.dsl.module
private val roomModule = module {
single {
Room.databaseBuilder(androidContext(), RetroDatabase::class.java, "playlist.db")
.allowMainThreadQueries()
.addCallback(object : RoomDatabase.Callback() {
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
GlobalScope.launch(IO) {
FilePathUtil.blacklistFilePaths().map {
get<PlaylistDao>().insertBlacklistPath(BlackListStoreEntity(it))
}
}
}
})
.fallbackToDestructiveMigration()
.build()
}
factory {
get<RetroDatabase>().playlistDao()
}
single {
RealRoomRepository(get())
} bind RoomPlaylistRepository::class
}
private val mainModule = module {
single {
androidContext().contentResolver
@ -70,14 +104,6 @@ private val dataModule = module {
get()
)
}
single {
PlaylistDatabase.getDatabase(get()).playlistDao()
}
single {
RealRoomPlaylistRepository(get())
} bind RoomPlaylistRepository::class
}
private val viewModules = module {
@ -119,4 +145,4 @@ private val viewModules = module {
}
}
val appModules = listOf(mainModule, dataModule, viewModules, networkModule)
val appModules = listOf(mainModule, dataModule, viewModules, networkModule, roomModule)

View file

@ -218,7 +218,7 @@ class DriveModeActivity : AbsMusicServiceActivity(), Callback {
.build()
.transform(BlurTransformation.Builder(this).build())
.into(object : RetroMusicColoredTarget(image) {
override fun onColorReady(color: MediaNotificationProcessor) {
override fun onColorReady(colors: MediaNotificationProcessor) {
}
})
}

View file

@ -108,7 +108,7 @@ class MainActivity : AbsSlidingMusicPanelActivity(), OnSharedPreferenceChangeLis
if (intent.action != null && (intent.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH)
) {
val songs: List<Song> =
getSongs(this, intent.extras!!)
getSongs(intent.extras!!)
if (shuffleMode == MusicService.SHUFFLE_MODE_SHUFFLE) {
openAndShuffleQueue(songs, true)
} else {

View file

@ -104,7 +104,7 @@ open class PlayingQueueActivity : AbsMusicServiceActivity() {
}
}
})
val fastScroller = ThemedFastScroller.create(recyclerView)
ThemedFastScroller.create(recyclerView)
}
private fun checkForPadding() {

View file

@ -108,7 +108,7 @@ class PurchaseActivity : AbsBaseActivity(), BillingProcessor.IBillingHandler {
super.onDestroy()
}
private class RestorePurchaseAsyncTask internal constructor(purchaseActivity: PurchaseActivity) :
private class RestorePurchaseAsyncTask(purchaseActivity: PurchaseActivity) :
AsyncTask<Void, Void, Boolean>() {
private val buyActivityWeakReference: WeakReference<PurchaseActivity> = WeakReference(

View file

@ -110,7 +110,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
}
if (requestCode == TEZ_REQUEST_CODE) {
// Process based on the data in response.
Log.d("result", data!!.getStringExtra("Status"))
//Log.d("result", data!!.getStringExtra("Status"))
}
}
@ -121,7 +121,7 @@ class SupportDevelopmentActivity : AbsBaseActivity(), BillingProcessor.IBillingH
}
}
private class SkuDetailsLoadAsyncTask internal constructor(supportDevelopmentActivity: SupportDevelopmentActivity) :
private class SkuDetailsLoadAsyncTask(supportDevelopmentActivity: SupportDevelopmentActivity) :
AsyncTask<Void, Void, List<SkuDetails>>() {
private val weakReference: WeakReference<SupportDevelopmentActivity> = WeakReference(

View file

@ -160,7 +160,7 @@ class UserInfoActivity : AbsBaseActivity() {
}
private fun saveImage(bitmap: Bitmap, fileName: String) {
CoroutineScope(Dispatchers.IO).launch() {
CoroutineScope(Dispatchers.IO).launch {
val appDir = applicationContext.filesDir
val file = File(appDir, fileName)
var successful = false

View file

@ -28,7 +28,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.android.synthetic.main.sliding_music_panel_layout.*
import org.koin.androidx.viewmodel.ext.android.viewModel
abstract class AbsSlidingMusicPanelActivity() : AbsMusicServiceActivity() {
abstract class AbsSlidingMusicPanelActivity : AbsMusicServiceActivity() {
companion object {
val TAG: String = AbsSlidingMusicPanelActivity::class.java.simpleName
}

View file

@ -280,22 +280,22 @@ open class BugReportActivity : AbsThemeActivity() {
RESULT_BAD_CREDENTIALS -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_wrong_credentials)
.setPositiveButton(android.R.string.ok, null)
.setPositiveButton(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(android.R.string.ok, null).show()
.setPositiveButton(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(android.R.string.ok, null)
.setPositiveButton(R.string.ok, null)
else -> MaterialAlertDialogBuilder(context)
.setTitle(R.string.bug_report_failed)
.setMessage(R.string.bug_report_failed_unknown)
.setPositiveButton(android.R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> { tryToFinishActivity() } }
.setPositiveButton(R.string.ok) { _, _ -> tryToFinishActivity() }
.setNegativeButton(android.R.string.cancel) { _, _ -> tryToFinishActivity() }
}
}

View file

@ -44,9 +44,9 @@ class AlbumTagEditorActivity : AbsTagEditorActivity(), TextWatcher {
window.enterTransition = slide
}
override fun loadImageFromFile(selectedFileUri: Uri?) {
override fun loadImageFromFile(selectedFile: Uri?) {
Glide.with(this@AlbumTagEditorActivity).load(selectedFileUri).asBitmap()
Glide.with(this@AlbumTagEditorActivity).load(selectedFile).asBitmap()
.transcode(BitmapPaletteTranscoder(this), BitmapPaletteWrapper::class.java)
.diskCacheStrategy(DiskCacheStrategy.NONE).skipMemoryCache(true)
.into(object : SimpleTarget<BitmapPaletteWrapper>() {

View file

@ -1,5 +1,6 @@
package code.name.monkey.retromusic.adapter
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -22,14 +23,14 @@ class GenreAdapter(
var dataSet: List<Genre>,
private val mItemLayoutRes: Int
) : RecyclerView.Adapter<GenreAdapter.ViewHolder>() {
val colors = listOf<Int>(Color.RED, Color.BLUE)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(activity).inflate(mItemLayoutRes, parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val genre = dataSet[position]
holder.title?.text = genre.name
holder.text?.text = String.format(
Locale.getDefault(),

View file

@ -40,8 +40,8 @@ class HomeAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val layout = LayoutInflater.from(activity)
.inflate(R.layout.section_recycler_view, parent, false)
val layout =
LayoutInflater.from(activity).inflate(R.layout.section_recycler_view, parent, false)
return when (viewType) {
RECENT_ARTISTS, TOP_ARTISTS -> ArtistViewHolder(layout)
GENRES -> GenreViewHolder(layout)
@ -64,7 +64,7 @@ class HomeAdapter(
when (getItemViewType(position)) {
RECENT_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home.arrayList as List<Album>, R.string.recent_albums)
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
@ -74,7 +74,7 @@ class HomeAdapter(
}
TOP_ALBUMS -> {
val viewHolder = holder as AlbumViewHolder
viewHolder.bindView(home.arrayList as List<Album>, R.string.top_albums)
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
@ -84,7 +84,7 @@ class HomeAdapter(
}
RECENT_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home.arrayList, R.string.recent_artists)
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
@ -94,7 +94,7 @@ class HomeAdapter(
}
TOP_ARTISTS -> {
val viewHolder = holder as ArtistViewHolder
viewHolder.bindView(home.arrayList, R.string.top_artists)
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
@ -104,15 +104,21 @@ class HomeAdapter(
}
SUGGESTIONS -> {
val viewHolder = holder as SuggestionsViewHolder
viewHolder.bindView(home.arrayList)
viewHolder.bindView(home)
}
FAVOURITES -> {
val viewHolder = holder as PlaylistViewHolder
viewHolder.bindView(home.arrayList, R.string.favorites)
viewHolder.bindView(home)
viewHolder.clickableArea.setOnClickListener {
activity.findNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to FAVOURITES)
)
}
}
GENRES -> {
val viewHolder = holder as GenreViewHolder
viewHolder.bind(home.arrayList, R.string.genres)
viewHolder.bind(home)
}
PLAYLISTS -> {
@ -130,22 +136,22 @@ class HomeAdapter(
}
private inner class AlbumViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(albums: List<Album>, titleRes: Int) {
title.text = activity.getString(titleRes)
fun bindView(home: Home) {
title.setText(home.titleRes)
recyclerView.apply {
adapter = albumAdapter(albums)
adapter = albumAdapter(home.arrayList as List<Album>)
layoutManager = gridLayoutManager()
}
}
}
private inner class ArtistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(artists: List<Any>, titleRes: Int) {
fun bindView(home: Home) {
title.setText(home.titleRes)
recyclerView.apply {
layoutManager = linearLayoutManager()
adapter = artistsAdapter(artists as List<Artist>)
adapter = artistsAdapter(home.arrayList as List<Artist>)
}
title.text = activity.getString(titleRes)
}
}
@ -161,8 +167,7 @@ class HomeAdapter(
R.id.image8
)
fun bindView(songs: List<Any>) {
songs as List<Song>
fun bindView(home: Home) {
val color = ThemeStore.accentColor(activity)
itemView.findViewById<TextView>(R.id.message).setTextColor(color)
itemView.findViewById<MaterialCardView>(R.id.card6).apply {
@ -170,9 +175,9 @@ class HomeAdapter(
}
images.forEachIndexed { index, id ->
itemView.findViewById<View>(id).setOnClickListener {
MusicPlayerRemote.playNext(songs[index])
MusicPlayerRemote.playNext(home.arrayList[index] as Song)
}
SongGlideRequest.Builder.from(Glide.with(activity), songs[index])
SongGlideRequest.Builder.from(Glide.with(activity), home.arrayList[index] as Song)
.asBitmap()
.build()
.into(itemView.findViewById(id))
@ -182,35 +187,37 @@ class HomeAdapter(
}
private inner class PlaylistViewHolder(view: View) : AbsHomeViewItem(view) {
fun bindView(songs: List<Any>, titleRes: Int) {
arrow.hide()
fun bindView(home: Home) {
title.setText(home.titleRes)
recyclerView.apply {
val songAdapter = SongAdapter(
activity,
songs as MutableList<Song>,
home.arrayList as MutableList<Song>,
R.layout.item_album_card, null
)
layoutManager = linearLayoutManager()
adapter = songAdapter
}
title.text = activity.getString(titleRes)
}
}
private inner class GenreViewHolder(itemView: View) : AbsHomeViewItem(itemView) {
fun bind(genres: List<Any>, titleRes: Int) {
fun bind(home: Home) {
arrow.hide()
title.text = activity.getString(titleRes)
title.setText(home.titleRes)
val genreAdapter = GenreAdapter(
activity,
home.arrayList as List<Genre>,
R.layout.item_grid_genre
)
recyclerView.apply {
layoutManager = GridLayoutManager(activity, 3, GridLayoutManager.HORIZONTAL, false)
val genreAdapter =
GenreAdapter(activity, genres as List<Genre>, R.layout.item_grid_genre)
adapter = genreAdapter
}
}
}
open inner class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
open class AbsHomeViewItem(itemView: View) : RecyclerView.ViewHolder(itemView) {
val recyclerView: RecyclerView = itemView.findViewById(R.id.recyclerView)
val title: AppCompatTextView = itemView.findViewById(R.id.title)
val arrow: ImageView = itemView.findViewById(R.id.arrow)

View file

@ -3,7 +3,6 @@ package code.name.monkey.retromusic.adapter.album
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.FragmentActivity
import code.name.monkey.appthemehelper.util.ATHUtil
import code.name.monkey.retromusic.fragments.albums.AlbumClickListener
import code.name.monkey.retromusic.glide.AlbumGlideRequest
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
@ -30,8 +29,8 @@ class HorizontalAlbumAdapter(
}
override fun setColors(color: MediaNotificationProcessor, holder: ViewHolder) {
holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
//holder.title?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorPrimary))
//holder.text?.setTextColor(ATHUtil.resolveColor(activity, android.R.attr.textColorSecondary))
}
override fun loadAlbumCover(album: Album, holder: ViewHolder) {

View file

@ -6,6 +6,7 @@ import androidx.fragment.app.FragmentActivity
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.R.menu
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.toSongs
import code.name.monkey.retromusic.dialogs.RemoveSongFromPlaylistDialog
import code.name.monkey.retromusic.interfaces.CabHolder
import code.name.monkey.retromusic.model.PlaylistSong
@ -56,7 +57,7 @@ class OrderablePlaylistSongAdapter(
override fun onMultipleItemAction(menuItem: MenuItem, selection: List<Song>) {
when (menuItem.itemId) {
R.id.action_remove_from_playlist -> {
RemoveSongFromPlaylistDialog.create(selection.to(playlist.playListId))
RemoveSongFromPlaylistDialog.create(selection.toSongs(playlist.playListId))
.show(activity.supportFragmentManager, "REMOVE_FROM_PLAYLIST")
return
}

View file

@ -169,7 +169,7 @@ class PlayingQueueAdapter(
holder: ViewHolder?,
position: Int, @SwipeableItemResults result: Int
): SwipeResultAction {
return if (result === SwipeableItemConstants.RESULT_CANCELED) {
return if (result == SwipeableItemConstants.RESULT_CANCELED) {
SwipeResultActionDefault()
} else {
SwipedResultActionRemoveItem(this, position, activity)

View file

@ -0,0 +1,10 @@
package code.name.monkey.retromusic.db
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class BlackListStoreEntity(
@PrimaryKey
val path: String
)

View file

@ -0,0 +1,32 @@
package code.name.monkey.retromusic.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class HistoryEntity(
@PrimaryKey
val id: Int,
val title: String,
@ColumnInfo(name = "track_number")
val trackNumber: Int,
val year: Int,
val duration: Long,
val data: String,
@ColumnInfo(name = "date_modified")
val dateModified: Long,
@ColumnInfo(name = "album_id")
val albumId: Int,
@ColumnInfo(name = "album_name")
val albumName: String,
@ColumnInfo(name = "artist_id")
val artistId: Int,
@ColumnInfo(name = "artist_name")
val artistName: String,
val composer: String?,
@ColumnInfo(name = "album_artist")
val albumArtist: String?,
@ColumnInfo(name = "time_played")
val timePlayed: Long
)

View file

@ -0,0 +1,34 @@
package code.name.monkey.retromusic.db
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
class PlayCountEntity(
@PrimaryKey
val id: Int,
val title: String,
@ColumnInfo(name = "track_number")
val trackNumber: Int,
val year: Int,
val duration: Long,
val data: String,
@ColumnInfo(name = "date_modified")
val dateModified: Long,
@ColumnInfo(name = "album_id")
val albumId: Int,
@ColumnInfo(name = "album_name")
val albumName: String,
@ColumnInfo(name = "artist_id")
val artistId: Int,
@ColumnInfo(name = "artist_name")
val artistName: String,
val composer: String?,
@ColumnInfo(name = "album_artist")
val albumArtist: String?,
@ColumnInfo(name = "time_played")
val timePlayed: Long,
@ColumnInfo(name = "play_count")
var playCount: Int
)

View file

@ -1,5 +1,6 @@
package code.name.monkey.retromusic.db
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
@ -8,37 +9,84 @@ interface PlaylistDao {
suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long
@Query("UPDATE PlaylistEntity SET playlist_name = :name WHERE playlist_id = :playlistId")
suspend fun renamePlaylistEntity(playlistId: Int, name: String)
suspend fun renamePlaylist(playlistId: Int, name: String)
@Query("SELECT * FROM PlaylistEntity WHERE playlist_name = :name")
suspend fun checkPlaylistExists(name: String): List<PlaylistEntity>
fun isPlaylistExists(name: String): List<PlaylistEntity>
@Query("SELECT * FROM PlaylistEntity")
suspend fun playlists(): List<PlaylistEntity>
@Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId")
suspend fun deleteSongsFromPlaylist(playlistId: Int)
suspend fun deleteSongsInPlaylist(playlistId: Int)
@Query("DELETE FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun removeSongFromPlaylist(playlistId: Int, songId: Int)
@Transaction
@Query("SELECT * FROM PlaylistEntity")
suspend fun playlistsWithSong(): List<PlaylistWithSongs>
suspend fun playlistsWithSongs(): List<PlaylistWithSongs>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSongs(songEntities: List<SongEntity>)
suspend fun insertSongsToPlaylist(songEntities: List<SongEntity>)
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistName AND id = :songId")
suspend fun checkSongExistsWithPlaylistName(playlistName: String, songId: Int): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId AND id = :songId")
suspend fun isSongExistsInPlaylist(playlistId: Int, songId: Int): List<SongEntity>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id = :playlistId")
suspend fun getSongs(playlistId: Int): List<SongEntity>
suspend fun songsFromPlaylist(playlistId: Int): List<SongEntity>
@Delete
suspend fun deletePlaylistEntity(playlistEntity: PlaylistEntity)
suspend fun deletePlaylist(playlistEntity: PlaylistEntity)
@Delete
suspend fun deletePlaylistEntities(playlistEntities: List<PlaylistEntity>)
suspend fun deletePlaylists(playlistEntities: List<PlaylistEntity>)
@Delete
suspend fun removeSongsFromPlaylist(songs: List<SongEntity>)
suspend fun deleteSongsInPlaylist(songs: List<SongEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertSongInHistory(historyEntity: HistoryEntity)
@Query("SELECT * FROM HistoryEntity WHERE id = :songId LIMIT 1")
suspend fun isSongPresentInHistory(songId: Int): HistoryEntity?
@Update
suspend fun updateHistorySong(historyEntity: HistoryEntity)
@Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC")
fun historySongs(): LiveData<List<HistoryEntity>>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongsLiveData(playlistId: Int): LiveData<List<SongEntity>>
@Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongs(playlistId: Int): List<SongEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertSongInPlayCount(playCountEntity: PlayCountEntity)
@Update
fun updateSongInPlayCount(playCountEntity: PlayCountEntity)
@Delete
fun deleteSongInPlayCount(playCountEntity: PlayCountEntity)
@Query("SELECT * FROM PlayCountEntity WHERE id =:songId")
fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity>
@Query("SELECT * FROM PlayCountEntity ORDER BY play_count DESC")
fun playCountSongs(): List<PlayCountEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertBlacklistPath(blackListStoreEntities: List<BlackListStoreEntity>)
@Delete
suspend fun deleteBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
@Query("DELETE FROM BlackListStoreEntity")
suspend fun clearBlacklist()
}

View file

@ -1,34 +0,0 @@
package code.name.monkey.retromusic.db
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(
entities = [PlaylistEntity::class, SongEntity::class],
version = 8,
exportSchema = false
)
abstract class PlaylistDatabase : RoomDatabase() {
abstract fun playlistDao(): PlaylistDao
companion object {
@Volatile
private var INSTANCE: PlaylistDatabase? = null
fun getDatabase(
context: Context
): PlaylistDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PlaylistDatabase::class.java,
"playlists.db"
).fallbackToDestructiveMigration().build()
INSTANCE = instance
instance
}
}
}
}

View file

@ -13,5 +13,5 @@ data class PlaylistWithSongs(
entityColumn = "playlist_creator_id"
)
val songs: List<SongEntity>
):Parcelable
) : Parcelable

View file

@ -0,0 +1,13 @@
package code.name.monkey.retromusic.db
import androidx.room.Database
import androidx.room.RoomDatabase
@Database(
entities = [PlaylistEntity::class, SongEntity::class, HistoryEntity::class, PlayCountEntity::class, BlackListStoreEntity::class],
version = 18,
exportSchema = false
)
abstract class RetroDatabase : RoomDatabase() {
abstract fun playlistDao(): PlaylistDao
}

View file

@ -4,7 +4,6 @@ import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import code.name.monkey.retromusic.model.Song
import kotlinx.android.parcel.Parcelize
@Parcelize
@ -37,27 +36,5 @@ class SongEntity(
@ColumnInfo(name = "song_key")
var songPrimaryKey: Long = 0
fun toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
}
fun List<SongEntity>.toSongs(): List<Song> {
return map {
it.toSong()
}
}

View file

@ -0,0 +1,83 @@
package code.name.monkey.retromusic.db
import code.name.monkey.retromusic.model.Song
fun List<SongEntity>.toSongs(): List<Song> {
return map {
it.toSong()
}
}
fun SongEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun PlayCountEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun HistoryEntity.toSong(): Song {
return Song(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist
)
}
fun Song.toPlayCount(): PlayCountEntity {
return PlayCountEntity(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist,
System.currentTimeMillis(),
1
)
}

View file

@ -13,7 +13,7 @@ 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.fragments.ReloadType
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealRepository
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
@ -59,7 +59,7 @@ class AddToRetroPlaylist : BottomSheetDialogFragment() {
lifecycleScope.launch(Dispatchers.IO) {
val songEntities = songs.toSongEntity(playlistEntities[which - 1])
repository.insertSongs(songEntities)
libraryViewModel.forceReload(ReloadType.Playlists)
libraryViewModel.forceReload(Playlists)
}
}
dismiss()

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(android.R.string.ok)
.positiveText(R.string.ok)
.build();
}
if (savedInstanceState == null) {

View file

@ -42,24 +42,25 @@ class DeleteSongsDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val songs = extraNotNull<List<Song>>(EXTRA_SONG).value
var title = 0
var message: CharSequence = ""
if (songs.size > 1) {
title = R.string.delete_songs_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
val pair = if (songs.size > 1) {
Pair(
R.string.delete_songs_title, HtmlCompat.fromHtml(
String.format(getString(R.string.delete_x_songs), songs.size),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
} else {
title = R.string.delete_song_title
message = HtmlCompat.fromHtml(
String.format(getString(R.string.delete_song_x), songs[0].title),
HtmlCompat.FROM_HTML_MODE_LEGACY
Pair(
R.string.delete_song_title,
HtmlCompat.fromHtml(
String.format(getString(R.string.delete_song_x), songs[0].title),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
}
return materialDialog(title)
.setMessage(message)
return materialDialog(pair.first)
.setMessage(pair.second)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.action_delete) { _, _ ->

View file

@ -66,7 +66,8 @@ class RemoveSongFromPlaylistDialog : DialogFragment() {
.setMessage(pair.second)
.setPositiveButton(R.string.remove_action) { _, _ ->
lifecycleScope.launch(Dispatchers.IO) {
repository.removeSongFromPlaylist(songs)
//repository.removeSongFromPlaylist(songs)
repository.deleteSongsInPlaylist(songs)
libraryViewModel.forceReload(Playlists)
}
}

View file

@ -8,6 +8,4 @@ class RetroSingleCheckedListAdapter(
context: Context,
resource: Int = R.layout.dialog_list_item,
objects: MutableList<String>
) : ArrayAdapter<String>(context, resource, objects) {
}
) : ArrayAdapter<String>(context, resource, objects)

View file

@ -158,7 +158,7 @@ class SleepTimerDialog : DialogFragment() {
}
}
private inner class TimerUpdater internal constructor() :
private inner class TimerUpdater() :
CountDownTimer(
PreferenceUtil.nextSleepTimerElapsedRealTime - SystemClock.elapsedRealtime(),
1000

View file

@ -21,8 +21,6 @@ import android.os.Bundle
import android.text.Spanned
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.annotation.NonNull
import androidx.core.text.HtmlCompat
@ -41,12 +39,6 @@ import org.jaudiotagger.tag.TagException
import java.io.File
import java.io.IOException
inline fun ViewGroup.forEach(action: (View) -> Unit) {
for (i in 0 until childCount) {
action(getChildAt(i))
}
}
class SongDetailDialog : DialogFragment() {
@SuppressLint("InflateParams")
@ -152,7 +144,7 @@ class SongDetailDialog : DialogFragment() {
}
}
return materialDialog(R.string.action_details)
.setPositiveButton(android.R.string.ok, null)
.setPositiveButton(R.string.ok, null)
.setView(dialogView)
.create()
.colorButtons()

View file

@ -15,13 +15,8 @@
package code.name.monkey.retromusic.extensions
import android.app.Activity
import androidx.annotation.IdRes
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import code.name.monkey.appthemehelper.util.ToolbarContentTintHelper
import code.name.monkey.retromusic.R
import com.google.android.material.appbar.MaterialToolbar
fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
@ -30,41 +25,6 @@ fun AppCompatActivity.applyToolbar(toolbar: MaterialToolbar) {
setSupportActionBar(toolbar)
}
fun FragmentActivity?.addFragment(
@IdRes idRes: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this as? AppCompatActivity ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
add(fragment, tag)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
if (addToBackStack) {
addToBackStack(null)
}
commitNow()
}
}
fun AppCompatActivity.replaceFragment(
@IdRes id: Int = R.id.container,
fragment: Fragment,
tag: String? = null,
addToBackStack: Boolean = false
) {
val compatActivity = this ?: return
compatActivity.supportFragmentManager.beginTransaction()
.apply {
replace(id, fragment, tag)
if (addToBackStack) {
addToBackStack(null)
}
commit()
}
}
inline fun <reified T : Any> Activity.extra(key: String, default: T? = null) = lazy {
val value = intent?.extras?.get(key)
if (value is T) value else default

View file

@ -23,6 +23,7 @@ import android.widget.CheckBox
import android.widget.SeekBar
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatImageView
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.Fragment
import code.name.monkey.appthemehelper.ThemeStore
@ -130,6 +131,12 @@ fun MaterialButton.applyColor(color: Int) {
iconTint = textColorColorStateList
}
fun MaterialButton.applyOutlineColor(color: Int) {
val textColorColorStateList = ColorStateList.valueOf(color)
setTextColor(textColorColorStateList)
iconTint = textColorColorStateList
}
fun TextInputLayout.accentColor() {
val accentColor = ThemeStore.accentColor(context)
val colorState = ColorStateList.valueOf(accentColor)
@ -140,4 +147,8 @@ fun TextInputLayout.accentColor() {
fun TextInputEditText.accentColor() {
}
fun AppCompatImageView.accentColor(): Int {
return ThemeStore.accentColor(context)
}

View file

@ -51,7 +51,8 @@ val FragmentManager.currentNavigationFragment: Fragment?
get() = primaryNavigationFragment?.childFragmentManager?.fragments?.first()
fun AppCompatActivity.currentFragment(navHostId: Int): Fragment? {
val navHostFragment: NavHostFragment = supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
val navHostFragment: NavHostFragment =
supportFragmentManager.findFragmentById(navHostId) as NavHostFragment
navHostFragment.targetFragment
return navHostFragment.childFragmentManager.fragments.first()
}

View file

@ -14,13 +14,10 @@
package code.name.monkey.retromusic.extensions
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.EditText
import android.widget.SeekBar
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.appthemehelper.util.TintHelper

View file

@ -3,6 +3,8 @@ package code.name.monkey.retromusic.fragments
import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
@ -10,15 +12,14 @@ import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.adapter.album.AlbumAdapter
import code.name.monkey.retromusic.adapter.artist.ArtistAdapter
import code.name.monkey.retromusic.adapter.song.SongAdapter
import code.name.monkey.retromusic.db.toSong
import code.name.monkey.retromusic.fragments.albums.AlbumClickListener
import code.name.monkey.retromusic.fragments.artists.ArtistClickListener
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.android.synthetic.main.fragment_playlist_detail.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
@ -48,32 +49,86 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
RECENT_ALBUMS -> {
loadAlbums(R.string.recent_albums, RECENT_ALBUMS)
}
FAVOURITES -> {
loadFavorite()
}
FAVOURITES -> loadFavorite()
HISTORY_PLAYLIST -> loadHistory()
LAST_ADDED_PLAYLIST -> lastAddedSongs()
TOP_PLAYED_PLAYLIST -> topPlayed()
}
}
private fun lastAddedSongs() {
toolbar.setTitle(R.string.last_added)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.recentSongs()
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun topPlayed() {
toolbar.setTitle(R.string.my_top_tracks)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
lifecycleScope.launch(IO) {
val songs = repository.recentSongs()
withContext(Main) { songAdapter.swapDataSet(songs) }
}
}
private fun loadHistory() {
toolbar.setTitle(R.string.history)
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
repository.historySong().observe(viewLifecycleOwner, Observer {
val songs = it.map { historyEntity -> historyEntity.toSong() }
songAdapter.swapDataSet(songs)
})
}
private fun loadFavorite() {
toolbar.setTitle(R.string.favorites)
CoroutineScope(IO).launch {
val songs = repository.favoritePlaylistHome()
withContext(Main) {
recyclerView.apply {
adapter = SongAdapter(
requireActivity(),
songs.arrayList as MutableList<Song>,
R.layout.item_list, null
)
layoutManager = linearLayoutManager()
}
}
val songAdapter = SongAdapter(
requireActivity(),
mutableListOf(),
R.layout.item_list, null
)
recyclerView.apply {
adapter = songAdapter
layoutManager = linearLayoutManager()
}
repository.favorites().observe(viewLifecycleOwner, Observer {
println(it.size)
val songs = it.map { songEntity -> songEntity.toSong() }
songAdapter.swapDataSet(songs)
})
}
private fun loadArtists(title: Int, type: Int) {
toolbar.setTitle(title)
CoroutineScope(IO).launch {
lifecycleScope.launch(IO) {
val artists =
if (type == TOP_ARTISTS) repository.topArtists() else repository.recentArtists()
withContext(Main) {
@ -87,7 +142,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
private fun loadAlbums(title: Int, type: Int) {
toolbar.setTitle(title)
CoroutineScope(IO).launch {
lifecycleScope.launch(IO) {
val albums =
if (type == TOP_ALBUMS) repository.topAlbums() else repository.recentAlbums()
withContext(Main) {

View file

@ -5,7 +5,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.toPlayCount
import code.name.monkey.retromusic.fragments.ReloadType.*
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.repository.RealRepository
@ -15,7 +17,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class LibraryViewModel(
private val realRepository: RealRepository
private val repository: RealRepository
) : ViewModel(), MusicServiceEventListener {
private val paletteColor = MutableLiveData<Int>()
@ -49,37 +51,37 @@ class LibraryViewModel(
artists.value = loadArtists.await()
playlists.value = loadPlaylists.await()
roomPlaylists.value = loadPlaylistsWithSongs.await()
//genres.value = loadGenres.await()
genres.value = loadGenres.await()
}
private val loadHome: Deferred<List<Home>>
get() = viewModelScope.async { realRepository.homeSections() }
get() = viewModelScope.async { repository.homeSections() }
private val loadSongs: Deferred<List<Song>>
get() = viewModelScope.async(IO) { realRepository.allSongs() }
get() = viewModelScope.async(IO) { repository.allSongs() }
private val loadAlbums: Deferred<List<Album>>
get() = viewModelScope.async(IO) {
realRepository.allAlbums()
repository.allAlbums()
}
private val loadArtists: Deferred<List<Artist>>
get() = viewModelScope.async(IO) {
realRepository.albumArtists()
repository.albumArtists()
}
private val loadPlaylists: Deferred<List<Playlist>>
get() = viewModelScope.async(IO) {
realRepository.allPlaylists()
repository.allPlaylists()
}
private val loadPlaylistsWithSongs: Deferred<List<PlaylistWithSongs>>
get() = viewModelScope.async(IO) {
realRepository.playlistWithSongs()
repository.playlistWithSongs()
}
private val loadGenres: Deferred<List<Genre>>
get() = viewModelScope.async(IO) {
realRepository.allGenres()
repository.allGenres()
}
@ -119,6 +121,22 @@ class LibraryViewModel(
override fun onPlayingMetaChanged() {
println("onPlayingMetaChanged")
viewModelScope.launch(IO) {
val entity = repository.songPresentInHistory(MusicPlayerRemote.currentSong)
if (entity != null) {
repository.updateHistorySong(MusicPlayerRemote.currentSong)
} else {
repository.addSongToHistory(MusicPlayerRemote.currentSong)
}
val songs = repository.checkSongExistInPlayCount(MusicPlayerRemote.currentSong.id)
if (songs.isNotEmpty()) {
repository.updateSongInPlayCount(songs.first().apply {
playCount += playCount + 1
})
} else {
repository.insertSongInPlayCount(MusicPlayerRemote.currentSong.toPlayCount())
}
}
}
override fun onPlayStateChanged() {

View file

@ -26,6 +26,7 @@ import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToRetroPlaylist
import code.name.monkey.retromusic.dialogs.DeleteSongsDialog
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.AlbumGlideRequest
@ -240,7 +241,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
private fun setColors(color: MediaNotificationProcessor) {
shuffleAction.applyColor(color.backgroundColor)
playAction.applyColor(color.backgroundColor)
playAction.applyOutlineColor(color.backgroundColor)
}
override fun onAlbumClick(albumId: Int, view: View) {

View file

@ -27,7 +27,7 @@ class AlbumDetailsViewModel(
fun getAlbum(): LiveData<Album> = _album
fun getArtist(): LiveData<Artist> = _artist
fun getAlbumInfo(): LiveData<LastFmAlbum> = _lastFmAlbum
fun getMoreAlbums(): LiveData<List<Album>> = _moreAlbums;
fun getMoreAlbums(): LiveData<List<Album>> = _moreAlbums
init {
loadAlbumDetails()

View file

@ -22,6 +22,7 @@ import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
import code.name.monkey.retromusic.dialogs.AddToRetroPlaylist
import code.name.monkey.retromusic.extensions.applyColor
import code.name.monkey.retromusic.extensions.applyOutlineColor
import code.name.monkey.retromusic.extensions.show
import code.name.monkey.retromusic.extensions.showToast
import code.name.monkey.retromusic.fragments.albums.AlbumClickListener
@ -72,7 +73,6 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
setupRecyclerView()
postponeEnterTransition()
detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer {
showArtist(it)
startPostponedEnterTransition()
})
@ -191,7 +191,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
private fun setColors(color: MediaNotificationProcessor) {
shuffleAction.applyColor(color.backgroundColor)
playAction.applyColor(color.backgroundColor)
playAction.applyOutlineColor(color.backgroundColor)
}
override fun onAlbumClick(albumId: Int, view: View) {

View file

@ -6,8 +6,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async

View file

@ -1,10 +1,8 @@
package code.name.monkey.retromusic.fragments.base
import android.annotation.SuppressLint
import android.content.ContentUris
import android.content.Intent
import android.media.MediaMetadataRetriever
import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
@ -22,29 +20,34 @@ import code.name.monkey.retromusic.EXTRA_ARTIST_ID
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.tageditor.AbsTagEditorActivity
import code.name.monkey.retromusic.activities.tageditor.SongTagEditorActivity
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.dialogs.*
import code.name.monkey.retromusic.extensions.hide
import code.name.monkey.retromusic.extensions.whichFragment
import code.name.monkey.retromusic.fragments.LibraryViewModel
import code.name.monkey.retromusic.fragments.ReloadType.Playlists
import code.name.monkey.retromusic.fragments.player.PlayerAlbumCoverFragment
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.interfaces.PaletteColorHolder
import code.name.monkey.retromusic.model.Song
import code.name.monkey.retromusic.model.lyrics.Lyrics
import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.*
import kotlinx.android.synthetic.main.shadow_statusbar_toolbar.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.get
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.sharedViewModel
import java.io.FileNotFoundException
abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragment(layout),
Toolbar.OnMenuItemClickListener, PaletteColorHolder, PlayerAlbumCoverFragment.Callbacks {
private var updateIsFavoriteTask: AsyncTask<*, *, *>? = null
private var updateLyricsAsyncTask: AsyncTask<*, *, *>? = null
private var playerAlbumCoverFragment: PlayerAlbumCoverFragment? = null
protected val libraryViewModel by sharedViewModel<LibraryViewModel>()
@ -70,7 +73,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
return true
}
R.id.action_add_to_playlist -> {
lifecycleScope.launch(Dispatchers.IO) {
lifecycleScope.launch(IO) {
val playlists = get<RealRepository>().roomPlaylists()
withContext(Dispatchers.Main) {
AddToRetroPlaylist.create(playlists, song)
@ -159,7 +162,18 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
}
protected open fun toggleFavorite(song: Song) {
MusicUtil.toggleFavorite(requireActivity(), song)
lifecycleScope.launch(IO) {
val playlist: PlaylistEntity = repository.favoritePlaylist().first()
val songEntity = song.toSongEntity(playlist.playListId)
val isFavorite = repository.isFavoriteSong(songEntity).isNotEmpty()
if (isFavorite) {
repository.removeSongFromPlaylist(songEntity)
} else {
repository.insertSongs(listOf(song.toSongEntity(playlist.playListId)))
libraryViewModel.forceReload(Playlists)
}
requireContext().sendBroadcast(Intent(MusicService.FAVORITE_STATE_CHANGED))
}
}
abstract fun playerToolbar(): Toolbar?
@ -182,84 +196,62 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
updateLyrics()
}
override fun onDestroyView() {
if (updateIsFavoriteTask != null && !updateIsFavoriteTask!!.isCancelled) {
updateIsFavoriteTask!!.cancel(true)
}
if (updateLyricsAsyncTask != null && !updateLyricsAsyncTask!!.isCancelled) {
updateLyricsAsyncTask!!.cancel(true)
}
super.onDestroyView()
}
@SuppressLint("StaticFieldLeak")
fun updateIsFavorite() {
if (updateIsFavoriteTask != null) {
updateIsFavoriteTask!!.cancel(false)
}
updateIsFavoriteTask = object : AsyncTask<Song, Void, Boolean>() {
override fun doInBackground(vararg params: Song): Boolean {
return MusicUtil.isFavorite(requireActivity(), params[0])
}
override fun onPostExecute(isFavorite: Boolean) {
val res = if (isFavorite)
R.drawable.ic_favorite
else
R.drawable.ic_favorite_border
lifecycleScope.launch(IO) {
val playlist: PlaylistEntity = repository.favoritePlaylist().first()
val song = MusicPlayerRemote.currentSong.toSongEntity(playlist.playListId)
val isFavorite = repository.isFavoriteSong(song).isNotEmpty()
withContext(Dispatchers.Main) {
val icon = if (isFavorite) R.drawable.ic_favorite else R.drawable.ic_favorite_border
val drawable =
RetroUtil.getTintedVectorDrawable(requireContext(), res, toolbarIconColor())
if (playerToolbar() != null && playerToolbar()!!.menu.findItem(R.id.action_toggle_favorite) != null)
playerToolbar()!!.menu.findItem(R.id.action_toggle_favorite).setIcon(drawable)
.title =
if (isFavorite) getString(R.string.action_remove_from_favorites) else getString(
R.string.action_add_to_favorites
)
}
}.execute(MusicPlayerRemote.currentSong)
}
@SuppressLint("StaticFieldLeak")
private fun updateLyrics() {
if (updateLyricsAsyncTask != null) updateLyricsAsyncTask!!.cancel(false)
updateLyricsAsyncTask = object : AsyncTask<Song, Void, Lyrics>() {
override fun onPreExecute() {
super.onPreExecute()
setLyrics(null)
}
override fun doInBackground(vararg params: Song): Lyrics? {
try {
var data: String? =
LyricUtil.getStringFromFile(params[0].title, params[0].artistName)
return if (TextUtils.isEmpty(data)) {
data = MusicUtil.getLyrics(params[0])
return if (TextUtils.isEmpty(data)) {
null
} else {
Lyrics.parse(params[0], data)
}
} else Lyrics.parse(params[0], data!!)
} catch (err: FileNotFoundException) {
return null
RetroUtil.getTintedVectorDrawable(requireContext(), icon, toolbarIconColor())
if (playerToolbar() != null) {
playerToolbar()?.menu?.findItem(R.id.action_toggle_favorite)
?.setIcon(drawable)?.title =
if (isFavorite) getString(R.string.action_remove_from_favorites)
else getString(R.string.action_add_to_favorites)
}
}
}
}
override fun onPostExecute(l: Lyrics?) {
setLyrics(l)
private fun updateLyrics() {
setLyrics(null)
lifecycleScope.launch(IO) {
val song = MusicPlayerRemote.currentSong
val lyrics = try {
var data: String? = LyricUtil.getStringFromFile(song.title, song.artistName)
if (TextUtils.isEmpty(data)) {
data = MusicUtil.getLyrics(song)
if (TextUtils.isEmpty(data)) {
null
} else {
Lyrics.parse(song, data)
}
} else Lyrics.parse(song, data!!)
} catch (err: FileNotFoundException) {
null
}
override fun onCancelled(s: Lyrics?) {
onPostExecute(null)
withContext(Main) {
setLyrics(lyrics)
}
}.execute(MusicPlayerRemote.currentSong)
}
}
open fun setLyrics(l: Lyrics?) {
}
private val repository by inject<RealRepository>()
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
lifecycleScope.launch(IO) {
if (repository.checkPlaylistExists(getString(R.string.favorites)).isEmpty()) {
repository.createPlaylist(PlaylistEntity(getString(R.string.favorites)))
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (PreferenceUtil.isFullScreenMode &&
@ -267,8 +259,7 @@ abstract class AbsPlayerFragment(@LayoutRes layout: Int) : AbsMainActivityFragme
) {
view.findViewById<View>(R.id.status_bar).visibility = View.GONE
}
playerAlbumCoverFragment =
childFragmentManager.findFragmentById(R.id.playerAlbumCoverFragment) as PlayerAlbumCoverFragment?
playerAlbumCoverFragment = whichFragment(R.id.playerAlbumCoverFragment)
playerAlbumCoverFragment?.setCallbacks(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)

View file

@ -290,7 +290,7 @@ public class FoldersFragment extends AbsMainActivityFragment implements
}
}
if (startIndex > -1) {
MusicPlayerRemote.INSTANCE.openQueue(songs, startIndex, true);
MusicPlayerRemote.openQueue(songs, startIndex, true);
} else {
final File finalFile = file1;
Snackbar.make(coordinatorLayout, Html.fromHtml(

View file

@ -36,7 +36,7 @@ class GenreDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playlist_
mainActivity.addMusicServiceEventListener(detailsViewModel)
mainActivity.setSupportActionBar(toolbar)
mainActivity.hideBottomBarVisibility(false)
progressIndicator.hide()
setupRecyclerView()
detailsViewModel.getSongs().observe(viewLifecycleOwner, androidx.lifecycle.Observer {
songs(it)

View file

@ -21,10 +21,11 @@ import android.view.View
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.retromusic.EXTRA_PLAYLIST
import code.name.monkey.retromusic.HISTORY_PLAYLIST
import code.name.monkey.retromusic.LAST_ADDED_PLAYLIST
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.TOP_PLAYED_PLAYLIST
import code.name.monkey.retromusic.adapter.HomeAdapter
import code.name.monkey.retromusic.extensions.findActivityNavController
import code.name.monkey.retromusic.fragments.LibraryViewModel
@ -32,9 +33,6 @@ import code.name.monkey.retromusic.fragments.base.AbsMainActivityFragment
import code.name.monkey.retromusic.glide.ProfileBannerGlideRequest
import code.name.monkey.retromusic.glide.UserProfileGlideRequest
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.smartplaylist.HistoryPlaylist
import code.name.monkey.retromusic.model.smartplaylist.LastAddedPlaylist
import code.name.monkey.retromusic.model.smartplaylist.TopTracksPlaylist
import code.name.monkey.retromusic.repository.Repository
import code.name.monkey.retromusic.util.NavigationUtil
import code.name.monkey.retromusic.util.PreferenceUtil
@ -74,15 +72,15 @@ class HomeFragment :
lastAdded.setOnClickListener {
findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment,
bundleOf(EXTRA_PLAYLIST to LastAddedPlaylist())
R.id.detailListFragment,
bundleOf("type" to LAST_ADDED_PLAYLIST)
)
}
topPlayed.setOnClickListener {
findActivityNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment,
bundleOf(EXTRA_PLAYLIST to TopTracksPlaylist())
R.id.detailListFragment,
bundleOf("type" to TOP_PLAYED_PLAYLIST)
)
}
@ -96,9 +94,9 @@ class HomeFragment :
}
history.setOnClickListener {
requireActivity().findNavController(R.id.fragment_container).navigate(
R.id.playlistDetailsFragment,
bundleOf(EXTRA_PLAYLIST to HistoryPlaylist())
findActivityNavController(R.id.fragment_container).navigate(
R.id.detailListFragment,
bundleOf("type" to HISTORY_PLAYLIST)
)
}

View file

@ -3,9 +3,7 @@ package code.name.monkey.retromusic.fragments.player.fit
import android.animation.ObjectAnimator
import android.graphics.PorterDuff
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.DecelerateInterpolator
import android.view.animation.LinearInterpolator
@ -26,7 +24,6 @@ import code.name.monkey.retromusic.helper.PlayPauseButtonOnClickHandler
import code.name.monkey.retromusic.misc.SimpleOnSeekbarChangeListener
import code.name.monkey.retromusic.service.MusicService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import code.name.monkey.retromusic.util.color.MediaNotificationProcessor
import kotlinx.android.synthetic.main.fragment_fit_playback_controls.*

View file

@ -127,7 +127,7 @@ class PlaylistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_playli
return String(Character.toChars(unicode))
}
public override fun onPause() {
override fun onPause() {
if (recyclerViewDragDropManager != null) {
recyclerViewDragDropManager!!.cancelDrag()
}

View file

@ -31,10 +31,11 @@ class PlaylistDetailsViewModel(
loadPlaylistSongs(playlist)
}
private fun loadPlaylistSongs(playlist: PlaylistWithSongs) = viewModelScope.launch(Dispatchers.IO) {
val songs: List<Song> = realRepository.playlistSongs(playlist)
withContext(Main) { _playListSongs.postValue(songs) }
}
private fun loadPlaylistSongs(playlist: PlaylistWithSongs) =
viewModelScope.launch(Dispatchers.IO) {
val songs: List<Song> = realRepository.playlistSongs(playlist)
withContext(Main) { _playListSongs.postValue(songs) }
}
override fun onMediaStoreChanged() {
/*if (playlist !is AbsCustomPlaylist) {

View file

@ -15,8 +15,8 @@
package code.name.monkey.retromusic.glide.artistimage
import android.content.Context
import code.name.monkey.retromusic.deezer.DeezerApiService
import code.name.monkey.retromusic.deezer.Data
import code.name.monkey.retromusic.deezer.DeezerApiService
import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Priority
@ -69,13 +69,13 @@ class ArtistImageFetcher(
val response = deezerApiService.getArtistImage(artists[0]).execute()
if (!response.isSuccessful) {
throw IOException("Request failed with code: " + response.code());
throw IOException("Request failed with code: " + response.code())
}
if (isCancelled) return null
return try {
val deezerResponse = response.body();
val deezerResponse = response.body()
val imageUrl = deezerResponse?.data?.get(0)?.let { getHighestQuality(it) }
// Fragile way to detect a place holder image returned from Deezer:
// ex: "https://e-cdns-images.dzcdn.net/images/artist//250x250-000000-80-0-0.jpg"

View file

@ -21,7 +21,7 @@ import code.name.monkey.retromusic.R
object HorizontalAdapterHelper {
const val LAYOUT_RES = R.layout.item_image
const val LAYOUT_RES = R.layout.item_album_card
private const val TYPE_FIRST = 1
private const val TYPE_MIDDLE = 2

View file

@ -13,7 +13,6 @@
*/
package code.name.monkey.retromusic.helper
import android.content.Context
import code.name.monkey.retromusic.model.Playlist
import java.io.BufferedWriter
import java.io.File
@ -24,14 +23,13 @@ object M3UWriter : M3UConstants {
@JvmStatic
@Throws(IOException::class)
fun write(
context: Context,
dir: File,
playlist: Playlist
): File? {
if (!dir.exists()) dir.mkdirs()
val file = File(dir, playlist.name + "." + M3UConstants.EXTENSION)
val songs = playlist.getSongs()
if (songs.size > 0) {
if (songs.isNotEmpty()) {
val bw = BufferedWriter(FileWriter(file))
bw.write(M3UConstants.HEADER)
for (song in songs) {

View file

@ -15,7 +15,6 @@
package code.name.monkey.retromusic.helper
import android.app.SearchManager
import android.content.Context
import android.os.Bundle
import android.provider.MediaStore
import code.name.monkey.retromusic.model.Song
@ -33,7 +32,7 @@ object SearchQueryHelper : KoinComponent {
var songs = ArrayList<Song>()
@JvmStatic
fun getSongs(context: Context, extras: Bundle): List<Song> {
fun getSongs(extras: Bundle): List<Song> {
val query = extras.getString(SearchManager.QUERY, null)
val artistName = extras.getString(MediaStore.EXTRA_MEDIA_ARTIST, null)
val albumName = extras.getString(MediaStore.EXTRA_MEDIA_ALBUM, null)
@ -45,9 +44,9 @@ object SearchQueryHelper : KoinComponent {
songRepository.makeSongCursor(
ARTIST_SELECTION + AND + ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(
artistName.toLowerCase(),
albumName.toLowerCase(),
titleName.toLowerCase()
artistName.toLowerCase(Locale.getDefault()),
albumName.toLowerCase(Locale.getDefault()),
titleName.toLowerCase(Locale.getDefault())
)
)
)
@ -59,7 +58,10 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION + AND + TITLE_SELECTION,
arrayOf(artistName.toLowerCase(), titleName.toLowerCase())
arrayOf(
artistName.toLowerCase(Locale.getDefault()),
titleName.toLowerCase(Locale.getDefault())
)
)
)
}
@ -70,7 +72,10 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION + AND + TITLE_SELECTION,
arrayOf(albumName.toLowerCase(), titleName.toLowerCase())
arrayOf(
albumName.toLowerCase(Locale.getDefault()),
titleName.toLowerCase(Locale.getDefault())
)
)
)
}
@ -81,7 +86,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION,
arrayOf(artistName.toLowerCase())
arrayOf(artistName.toLowerCase(Locale.getDefault()))
)
)
}
@ -92,7 +97,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION,
arrayOf(albumName.toLowerCase())
arrayOf(albumName.toLowerCase(Locale.getDefault()))
)
)
}
@ -103,7 +108,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
TITLE_SELECTION,
arrayOf(titleName.toLowerCase())
arrayOf(titleName.toLowerCase(Locale.getDefault()))
)
)
}
@ -113,7 +118,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ARTIST_SELECTION,
arrayOf(query.toLowerCase())
arrayOf(query.toLowerCase(Locale.getDefault()))
)
)
@ -123,7 +128,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
ALBUM_SELECTION,
arrayOf(query.toLowerCase())
arrayOf(query.toLowerCase(Locale.getDefault()))
)
)
if (songs.isNotEmpty()) {
@ -132,7 +137,7 @@ object SearchQueryHelper : KoinComponent {
songs = songRepository.songs(
songRepository.makeSongCursor(
TITLE_SELECTION,
arrayOf(query.toLowerCase())
arrayOf(query.toLowerCase(Locale.getDefault()))
)
)
return if (songs.isNotEmpty()) {

View file

@ -15,7 +15,6 @@
package code.name.monkey.retromusic.helper.menu
import android.app.Activity
import android.content.Context
import android.view.MenuItem
import android.widget.Toast
@ -72,7 +71,7 @@ object PlaylistMenuHelper : KoinComponent {
return true
}
R.id.action_rename_playlist -> {
RenameRetroPlaylistDialog.create(playlistWithSongs.playlistEntity )
RenameRetroPlaylistDialog.create(playlistWithSongs.playlistEntity)
.show(activity.supportFragmentManager, "RENAME_PLAYLIST")
return true
}
@ -90,7 +89,6 @@ object PlaylistMenuHelper : KoinComponent {
}
private fun getPlaylistSongs(
activity: Activity,
playlist: Playlist
): List<Song> {
return if (playlist is AbsCustomPlaylist) {
@ -100,7 +98,7 @@ object PlaylistMenuHelper : KoinComponent {
}
}
private class SavePlaylistAsyncTask internal constructor(context: Context) :
private class SavePlaylistAsyncTask(context: Context) :
WeakContextAsyncTask<Playlist, String, String>(context) {
override fun doInBackground(vararg params: Playlist): String {

View file

@ -11,20 +11,20 @@ public class Lrc {
private long time;
private String text;
public void setTime(long time) {
this.time = time;
}
public void setText(String text) {
this.text = text;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
}

View file

@ -23,6 +23,9 @@ import android.text.TextUtils;
* 一行歌词实体
*/
class LrcEntry implements Comparable<LrcEntry> {
public static final int GRAVITY_CENTER = 0;
public static final int GRAVITY_LEFT = 1;
public static final int GRAVITY_RIGHT = 2;
private long time;
private String text;
private String secondText;
@ -31,9 +34,6 @@ class LrcEntry implements Comparable<LrcEntry> {
* 歌词距离视图顶部的距离
*/
private float offset = Float.MIN_VALUE;
public static final int GRAVITY_CENTER = 0;
public static final int GRAVITY_LEFT = 1;
public static final int GRAVITY_RIGHT = 2;
LrcEntry(long time, String text) {
this.time = time;

View file

@ -28,6 +28,7 @@ import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -77,7 +78,7 @@ class LrcUtils {
List<LrcEntry> entryList = new ArrayList<>();
try {
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), "utf-8"));
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(lrcFile), StandardCharsets.UTF_8));
String line;
while ((line = br.readLine()) != null) {
List<LrcEntry> list = parseLine(line);

View file

@ -35,6 +35,8 @@ import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.Scroller;
import androidx.core.content.res.ResourcesCompat;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
@ -510,6 +512,7 @@ public class LrcView extends View {
if (i > 0) {
y += ((mLrcEntryList.get(i - 1).getHeight() + mLrcEntryList.get(i).getHeight()) >> 1) + mDividerHeight;
}
mLrcPaint.setTypeface(ResourcesCompat.getFont(getContext(), R.font.sans));
if (i == mCurrentLine) {
mLrcPaint.setTextSize(mCurrentTextSize);
mLrcPaint.setColor(mCurrentTextColor);

View file

@ -14,10 +14,13 @@
package code.name.monkey.retromusic.model
import androidx.annotation.StringRes
import code.name.monkey.retromusic.HomeSection
class Home(
val arrayList: List<Any>,
@HomeSection
val homeSection: Int
val homeSection: Int,
@StringRes
val titleRes: Int
)

View file

@ -14,6 +14,7 @@
package code.name.monkey.retromusic.model
import android.os.Parcelable
import code.name.monkey.retromusic.db.HistoryEntity
import code.name.monkey.retromusic.db.SongEntity
import kotlinx.android.parcel.Parcelize
@ -33,6 +34,25 @@ open class Song(
val composer: String?,
val albumArtist: String?
) : Parcelable {
fun toHistoryEntity(timePlayed: Long): HistoryEntity {
return HistoryEntity(
id,
title,
trackNumber,
year,
duration,
data,
dateModified,
albumId,
albumName,
artistId,
artistName,
composer,
albumArtist,
timePlayed
)
}
fun toSongEntity(playListId: Int): SongEntity {
return SongEntity(
playListId,

View file

@ -106,7 +106,7 @@ class AlbumCoverStylePreferenceDialog : DialogFragment(),
override fun onPageScrollStateChanged(state: Int) {
}
private class AlbumCoverStyleAdapter internal constructor(private val context: Context) :
private class AlbumCoverStyleAdapter(private val context: Context) :
PagerAdapter() {
override fun instantiateItem(collection: ViewGroup, position: Int): Any {

View file

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

View file

@ -108,7 +108,7 @@ public class SongPlayCountStore extends SQLiteOpenHelper {
*/
@NonNull
private static String getColumnNameForWeek(final int week) {
return SongPlayCountColumns.WEEK_PLAY_COUNT + String.valueOf(week);
return SongPlayCountColumns.WEEK_PLAY_COUNT + week;
}
/**

View file

@ -15,10 +15,9 @@
package code.name.monkey.retromusic.repository
import android.content.Context
import androidx.lifecycle.LiveData
import code.name.monkey.retromusic.*
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.SongEntity
import code.name.monkey.retromusic.db.*
import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.NotPlayedPlaylist
import code.name.monkey.retromusic.network.LastFMService
@ -32,91 +31,65 @@ import kotlinx.coroutines.flow.flow
interface Repository {
fun songsFlow(): Flow<Result<List<Song>>>
fun albumsFlow(): Flow<Result<List<Album>>>
fun artistsFlow(): Flow<Result<List<Artist>>>
fun playlistsFlow(): Flow<Result<List<Playlist>>>
fun genresFlow(): Flow<Result<List<Genre>>>
suspend fun allAlbums(): List<Album>
suspend fun albumById(albumId: Int): Album
suspend fun allSongs(): List<Song>
suspend fun allArtists(): List<Artist>
suspend fun albumArtists(): List<Artist>
suspend fun allPlaylists(): List<Playlist>
suspend fun allGenres(): List<Genre>
suspend fun search(query: String?): MutableList<Any>
suspend fun getPlaylistSongs(playlist: Playlist): List<Song>
suspend fun getGenre(genreId: Int): List<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist
suspend fun albumInfo(artist: String, album: String): LastFmAlbum
suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): List<Artist>
suspend fun topArtists(): List<Artist>
suspend fun topAlbums(): List<Album>
suspend fun recentAlbums(): List<Album>
suspend fun recentArtistsHome(): Home
suspend fun topArtistsHome(): Home
suspend fun topAlbumsHome(): Home
suspend fun recentAlbumsHome(): Home
suspend fun favoritePlaylistHome(): Home
suspend fun suggestionsHome(): Home
suspend fun genresHome(): Home
suspend fun playlists(): Home
suspend fun homeSections(): List<Home>
suspend fun homeSectionsFlow(): Flow<Result<List<Home>>>
suspend fun playlist(playlistId: Int): Playlist
suspend fun playlistWithSongs(): List<PlaylistWithSongs>
suspend fun playlistSongs(playlistWithSongs: PlaylistWithSongs): List<Song>
suspend fun insertSongs(songs: List<SongEntity>)
suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity>
suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long
suspend fun roomPlaylists(): List<PlaylistEntity>
suspend fun deleteRoomPlaylist(playlists: List<PlaylistEntity>)
suspend fun renameRoomPlaylist(playlistId: Int, name: String)
suspend fun removeSongFromPlaylist(songs: List<SongEntity>)
suspend fun deleteSongsInPlaylist(songs: List<SongEntity>)
suspend fun removeSongFromPlaylist(songEntity: SongEntity)
suspend fun deleteSongsFromPlaylist(playlists: List<PlaylistEntity>)
suspend fun favoritePlaylist(): List<PlaylistEntity>
suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity>
suspend fun addSongToHistory(currentSong: Song)
suspend fun songPresentInHistory(currentSong: Song): HistoryEntity?
suspend fun updateHistorySong(currentSong: Song)
suspend fun favoritePlaylistSongs(): List<SongEntity>
suspend fun recentSongs(): List<Song>
suspend fun topPlayedSongs(): List<Song>
suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity>
suspend fun playCountSongs(): List<PlayCountEntity>
fun historySong(): LiveData<List<HistoryEntity>>
fun favorites(): LiveData<List<SongEntity>>
}
class RealRepository(
@ -129,8 +102,8 @@ class RealRepository(
private val lastAddedRepository: LastAddedRepository,
private val playlistRepository: PlaylistRepository,
private val searchRepository: RealSearchRepository,
private val playedTracksRepository: TopPlayedRepository,
private val roomPlaylistRepository: RoomPlaylistRepository
private val topPlayedRepository: TopPlayedRepository,
private val roomRepository: RoomPlaylistRepository
) : Repository {
override suspend fun allAlbums(): List<Album> = albumRepository.albums()
@ -147,9 +120,9 @@ class RealRepository(
override suspend fun recentAlbums(): List<Album> = lastAddedRepository.recentAlbums()
override suspend fun topArtists(): List<Artist> = playedTracksRepository.topArtists()
override suspend fun topArtists(): List<Artist> = topPlayedRepository.topArtists()
override suspend fun topAlbums(): List<Album> = playedTracksRepository.topAlbums()
override suspend fun topAlbums(): List<Album> = topPlayedRepository.topAlbums()
override suspend fun allPlaylists(): List<Playlist> = playlistRepository.playlists()
@ -214,13 +187,14 @@ class RealRepository(
override suspend fun homeSections(): List<Home> {
val homeSections = mutableListOf<Home>()
val sections = listOf(
val sections: List<Home> = listOf(
suggestionsHome(),
topArtistsHome(),
topAlbumsHome(),
recentArtistsHome(),
recentAlbumsHome(),
suggestionsHome(),
favoritePlaylistHome()
favoritePlaylistHome(),
genresHome()
)
for (section in sections) {
if (section.arrayList.isNotEmpty()) {
@ -230,16 +204,12 @@ class RealRepository(
return homeSections
}
override suspend fun playlists(): Home {
val playlist = playlistRepository.playlists()
return Home(playlist, TOP_ALBUMS)
}
override suspend fun playlist(playlistId: Int) =
playlistRepository.playlist(playlistId)
override suspend fun playlistWithSongs(): List<PlaylistWithSongs> =
roomPlaylistRepository.playlistWithSongs()
roomRepository.playlistWithSongs()
override suspend fun playlistSongs(playlistWithSongs: PlaylistWithSongs): List<Song> {
return playlistWithSongs.songs.map {
@ -248,71 +218,118 @@ class RealRepository(
}
override suspend fun insertSongs(songs: List<SongEntity>) =
roomPlaylistRepository.insertSongs(songs)
roomRepository.insertSongs(songs)
override suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> =
roomPlaylistRepository.checkPlaylistExists(playlistName)
roomRepository.checkPlaylistExists(playlistName)
override suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long =
roomPlaylistRepository.createPlaylist(playlistEntity)
roomRepository.createPlaylist(playlistEntity)
override suspend fun roomPlaylists(): List<PlaylistEntity> = roomPlaylistRepository.playlists()
override suspend fun roomPlaylists(): List<PlaylistEntity> = roomRepository.playlists()
override suspend fun deleteRoomPlaylist(playlists: List<PlaylistEntity>) =
roomPlaylistRepository.deletePlaylistEntities(playlists)
roomRepository.deletePlaylistEntities(playlists)
override suspend fun renameRoomPlaylist(playlistId: Int, name: String) =
roomPlaylistRepository.renamePlaylistEntity(playlistId, name)
roomRepository.renamePlaylistEntity(playlistId, name)
override suspend fun removeSongFromPlaylist(songs: List<SongEntity>) =
roomPlaylistRepository.removeSongsFromPlaylist(songs)
override suspend fun deleteSongsInPlaylist(songs: List<SongEntity>) =
roomRepository.deleteSongsInPlaylist(songs)
override suspend fun removeSongFromPlaylist(songEntity: SongEntity) =
roomRepository.removeSongFromPlaylist(songEntity)
override suspend fun deleteSongsFromPlaylist(playlists: List<PlaylistEntity>) =
roomPlaylistRepository.deleteSongsFromPlaylist(playlists)
roomRepository.deleteSongsFromPlaylist(playlists)
override suspend fun favoritePlaylist(): List<PlaylistEntity> =
roomRepository.favoritePlaylist(context.getString(R.string.favorites))
override suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity> =
roomRepository.isFavoriteSong(songEntity)
override suspend fun addSongToHistory(currentSong: Song) =
roomRepository.addSongToHistory(currentSong)
override suspend fun songPresentInHistory(currentSong: Song): HistoryEntity? =
roomRepository.songPresentInHistory(currentSong)
override suspend fun updateHistorySong(currentSong: Song) =
roomRepository.updateHistorySong(currentSong)
override suspend fun favoritePlaylistSongs(): List<SongEntity> =
roomRepository.favoritePlaylistSongs(context.getString(R.string.favorites))
override suspend fun recentSongs(): List<Song> = lastAddedRepository.recentSongs()
override suspend fun topPlayedSongs(): List<Song> = topPlayedRepository.topTracks()
override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) =
roomRepository.insertSongInPlayCount(playCountEntity)
override suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) =
roomRepository.updateSongInPlayCount(playCountEntity)
override suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) =
roomRepository.deleteSongInPlayCount(playCountEntity)
override suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity> =
roomRepository.checkSongExistInPlayCount(songId)
override suspend fun playCountSongs(): List<PlayCountEntity> =
roomRepository.playCountSongs()
override fun historySong(): LiveData<List<HistoryEntity>> =
roomRepository.historySongs()
override fun favorites(): LiveData<List<SongEntity>> =
roomRepository.favoritePlaylistLiveData(context.getString(R.string.favorites))
override suspend fun suggestionsHome(): Home {
val songs =
NotPlayedPlaylist().songs().shuffled().takeIf {
it.size > 9
} ?: emptyList()
println(songs.size)
return Home(songs, SUGGESTIONS)
return Home(songs, SUGGESTIONS, R.string.suggestion_songs)
}
override suspend fun genresHome(): Home {
val genres = genreRepository.genres().shuffled()
return Home(genres, GENRES)
return Home(genres, GENRES, R.string.genres)
}
override suspend fun playlists(): Home {
val playlist = playlistRepository.playlists()
return Home(playlist, PLAYLISTS, R.string.playlists)
}
override suspend fun recentArtistsHome(): Home {
val artists = lastAddedRepository.recentArtists().take(5)
return Home(artists, RECENT_ARTISTS)
return Home(artists, RECENT_ARTISTS, R.string.recent_artists)
}
override suspend fun recentAlbumsHome(): Home {
val albums = lastAddedRepository.recentAlbums().take(5)
return Home(albums, RECENT_ALBUMS)
return Home(albums, RECENT_ALBUMS, R.string.recent_albums)
}
override suspend fun topAlbumsHome(): Home {
val albums = playedTracksRepository.topAlbums().take(5)
return Home(albums, TOP_ALBUMS)
val albums = topPlayedRepository.topAlbums().take(5)
return Home(albums, TOP_ALBUMS, R.string.top_albums)
}
override suspend fun topArtistsHome(): Home {
val artists = playedTracksRepository.topArtists().take(5)
return Home(artists, TOP_ARTISTS)
val artists = topPlayedRepository.topArtists().take(5)
return Home(artists, TOP_ARTISTS, R.string.top_artists)
}
override suspend fun favoritePlaylistHome(): Home {
val playlists =
playlistRepository.favoritePlaylist(context.getString(R.string.favorites)).take(5)
val songs = if (playlists.isNotEmpty())
PlaylistSongsLoader.getPlaylistSongList(context, playlists[0])
else emptyList()
return Home(songs, FAVOURITES)
val songs = favoritePlaylistSongs().map {
it.toSong()
}
println(songs.size)
return Home(songs, FAVOURITES, R.string.favorites)
}
override fun songsFlow(): Flow<Result<List<Song>>> = flow {

View file

@ -1,10 +1,9 @@
package code.name.monkey.retromusic.repository
import androidx.annotation.WorkerThread
import code.name.monkey.retromusic.db.PlaylistDao
import code.name.monkey.retromusic.db.PlaylistEntity
import code.name.monkey.retromusic.db.PlaylistWithSongs
import code.name.monkey.retromusic.db.SongEntity
import androidx.lifecycle.LiveData
import code.name.monkey.retromusic.db.*
import code.name.monkey.retromusic.model.Song
interface RoomPlaylistRepository {
@ -16,11 +15,25 @@ interface RoomPlaylistRepository {
suspend fun getSongs(playlistEntity: PlaylistEntity): List<SongEntity>
suspend fun deletePlaylistEntities(playlistEntities: List<PlaylistEntity>)
suspend fun renamePlaylistEntity(playlistId: Int, name: String)
suspend fun removeSongsFromPlaylist(songs: List<SongEntity>)
suspend fun deleteSongsInPlaylist(songs: List<SongEntity>)
suspend fun deleteSongsFromPlaylist(playlists: List<PlaylistEntity>)
suspend fun favoritePlaylist(favorite: String): List<PlaylistEntity>
suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity>
suspend fun removeSongFromPlaylist(songEntity: SongEntity)
suspend fun addSongToHistory(currentSong: Song)
suspend fun songPresentInHistory(song: Song): HistoryEntity?
suspend fun updateHistorySong(song: Song)
suspend fun favoritePlaylistSongs(favorite: String): List<SongEntity>
suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity)
suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity>
suspend fun playCountSongs(): List<PlayCountEntity>
fun historySongs(): LiveData<List<HistoryEntity>>
fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>>
}
class RealRoomPlaylistRepository(
class RealRoomRepository(
private val playlistDao: PlaylistDao
) : RoomPlaylistRepository {
@WorkerThread
@ -29,14 +42,14 @@ class RealRoomPlaylistRepository(
@WorkerThread
override suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> =
playlistDao.checkPlaylistExists(playlistName)
playlistDao.isPlaylistExists(playlistName)
@WorkerThread
override suspend fun playlists(): List<PlaylistEntity> = playlistDao.playlists()
@WorkerThread
override suspend fun playlistWithSongs(): List<PlaylistWithSongs> =
playlistDao.playlistsWithSong()
playlistDao.playlistsWithSongs()
@WorkerThread
override suspend fun insertSongs(songs: List<SongEntity>) {
@ -46,26 +59,77 @@ class RealRoomPlaylistRepository(
}.first()
println("Existing ${existingSongs.size}")
tempList.removeAll(existingSongs)*/
playlistDao.insertSongs(songs)
playlistDao.insertSongsToPlaylist(songs)
}
override suspend fun getSongs(playlistEntity: PlaylistEntity): List<SongEntity> {
return playlistDao.getSongs(playlistEntity.playListId)
return playlistDao.songsFromPlaylist(playlistEntity.playListId)
}
override suspend fun deletePlaylistEntities(playlistEntities: List<PlaylistEntity>) =
playlistDao.deletePlaylistEntities(playlistEntities)
playlistDao.deletePlaylists(playlistEntities)
override suspend fun renamePlaylistEntity(playlistId: Int, name: String) =
playlistDao.renamePlaylistEntity(playlistId, name)
playlistDao.renamePlaylist(playlistId, name)
override suspend fun removeSongsFromPlaylist(songs: List<SongEntity>) =
playlistDao.removeSongsFromPlaylist(songs)
override suspend fun deleteSongsInPlaylist(songs: List<SongEntity>) =
playlistDao.deleteSongsInPlaylist(songs)
override suspend fun deleteSongsFromPlaylist(playlists: List<PlaylistEntity>) {
playlists.forEach {
playlistDao.deleteSongsFromPlaylist(it.playListId)
playlistDao.deleteSongsInPlaylist(it.playListId)
}
}
override suspend fun favoritePlaylist(favorite: String): List<PlaylistEntity> =
playlistDao.isPlaylistExists(favorite)
override suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity> =
playlistDao.isSongExistsInPlaylist(
songEntity.playlistCreatorId,
songEntity.id
)
override suspend fun removeSongFromPlaylist(songEntity: SongEntity) =
playlistDao.removeSongFromPlaylist(songEntity.playlistCreatorId, songEntity.id)
override suspend fun addSongToHistory(currentSong: Song) =
playlistDao.insertSongInHistory(currentSong.toHistoryEntity(System.currentTimeMillis()))
override suspend fun songPresentInHistory(song: Song): HistoryEntity? =
playlistDao.isSongPresentInHistory(song.id)
override suspend fun updateHistorySong(song: Song) =
playlistDao.updateHistorySong(song.toHistoryEntity(System.currentTimeMillis()))
override fun historySongs(): LiveData<List<HistoryEntity>> {
return playlistDao.historySongs()
}
override fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> =
playlistDao.favoritesSongsLiveData(
playlistDao.isPlaylistExists(favorite).first().playListId
)
override suspend fun favoritePlaylistSongs(favorite: String): List<SongEntity> {
return if (playlistDao.isPlaylistExists(favorite).isNotEmpty())
playlistDao.favoritesSongs(
playlistDao.isPlaylistExists(favorite).first().playListId
) else emptyList()
}
override suspend fun insertSongInPlayCount(playCountEntity: PlayCountEntity) =
playlistDao.insertSongInPlayCount(playCountEntity)
override suspend fun updateSongInPlayCount(playCountEntity: PlayCountEntity) =
playlistDao.updateSongInPlayCount(playCountEntity)
override suspend fun deleteSongInPlayCount(playCountEntity: PlayCountEntity) =
playlistDao.deleteSongInPlayCount(playCountEntity)
override suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity> =
playlistDao.checkSongExistInPlayCount(songId)
override suspend fun playCountSongs(): List<PlayCountEntity> =
playlistDao.playCountSongs()
}

View file

@ -116,13 +116,13 @@ class RealTopPlayedRepository(
private fun makeTopTracksCursorImpl(): SortedLongCursor? {
// first get the top results ids from the internal database
val songs =
val cursor =
SongPlayCountStore.getInstance(context).getTopPlayedResults(NUMBER_OF_TOP_TRACKS)
songs.use { localSongs ->
cursor.use { songs ->
return makeSortedCursor(
localSongs,
localSongs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID)
songs,
songs.getColumnIndex(SongPlayCountStore.SongPlayCountColumns.ID)
)
}
}

View file

@ -22,8 +22,8 @@ import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.os.Build
import android.text.Html
import androidx.core.app.NotificationCompat
import androidx.core.text.HtmlCompat
import androidx.media.app.NotificationCompat.MediaStyle
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.activities.MainActivity
@ -132,9 +132,19 @@ class PlayingNotificationImpl : PlayingNotification() {
.setLargeIcon(bitmapFinal)
.setContentIntent(clickIntent)
.setDeleteIntent(deleteIntent)
.setContentTitle(Html.fromHtml("<b>" + song.title + "</b>"))
.setContentTitle(
HtmlCompat.fromHtml(
"<b>" + song.title + "</b>",
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
.setContentText(song.artistName)
.setSubText(Html.fromHtml("<b>" + song.albumName + "</b>"))
.setSubText(
HtmlCompat.fromHtml(
"<b>" + song.albumName + "</b>",
HtmlCompat.FROM_HTML_MODE_LEGACY
)
)
.setOngoing(isPlaying)
.setShowWhen(false)
.addAction(toggleFavorite)

View file

@ -162,7 +162,6 @@ public class AutoGeneratedPlaylistBitmap {
}
;
Log.d(TAG, "getBitmapCollection: smalltime = " + (System.currentTimeMillis() - start));
if (round)
return BitmapEditor.getRoundedCornerBitmap(bitmap, bitmap.getWidth() / 40);

View file

@ -83,16 +83,16 @@ public final class BitmapEditor {
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int a[] = new int[wh];
int[] r = new int[wh];
int[] g = new int[wh];
int[] b = new int[wh];
int[] a = new int[wh];
int rsum, gsum, bsum, asum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int[] vmin = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
int[] dv = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
@ -295,7 +295,7 @@ public final class BitmapEditor {
public static boolean PerceivedBrightness(int will_White, int[] c) {
double TBT = Math.sqrt(c[0] * c[0] * .241 + c[1] * c[1] * .691 + c[2] * c[2] * .068);
// Log.d("themee",TBT+"");
return (TBT > will_White) ? false : true;
return !(TBT > will_White);
}
public static int[] getAverageColorRGB(Bitmap bitmap) {
@ -404,15 +404,15 @@ public final class BitmapEditor {
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int[] r = new int[wh];
int[] g = new int[wh];
int[] b = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int[] vmin = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
int[] dv = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
@ -635,8 +635,7 @@ public final class BitmapEditor {
public static boolean TrueIfBitmapBigger(Bitmap bitmap, int size) {
int sizeBitmap = (bitmap.getHeight() > bitmap.getWidth()) ? bitmap.getHeight() : bitmap.getWidth();
if (sizeBitmap > size) return true;
return false;
return sizeBitmap > size;
}
public static Bitmap GetRoundedBitmapWithBlurShadow(Bitmap original, int paddingTop, int paddingBottom, int paddingLeft, int paddingRight) {

View file

@ -0,0 +1,16 @@
package code.name.monkey.retromusic.util
import android.os.Environment
import java.io.File
object FilePathUtil {
fun blacklistFilePaths(): List<String> {
return listOf<File>(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS),
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES),
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS)
).map {
FileUtil.safeGetCanonicalPath(it)
}
}
}

View file

@ -245,13 +245,9 @@ public final class FileUtil {
.equals(android.os.Environment.MEDIA_MOUNTED);
Boolean isSDSupportedDevice = Environment.isExternalStorageRemovable();
if (isSDSupportedDevice && isSDPresent) {
// yes SD-card is present
return true;
} else {
return false;
// Sorry
}
// yes SD-card is present
// Sorry
return isSDSupportedDevice && isSDPresent;
}
public static File safeGetCanonicalFile(File file) {

View file

@ -26,7 +26,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
/**
* Created by hefuyi on 2016/11/8.
@ -102,7 +102,7 @@ public class LyricUtil {
}
private static String getLrcOriginalPath(String filePath) {
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1, filePath.length()), "lrc");
return filePath.replace(filePath.substring(filePath.lastIndexOf(".") + 1), "lrc");
}
@NonNull
@ -110,16 +110,9 @@ public class LyricUtil {
if (str == null || str.length() == 0) {
return null;
}
try {
byte[] encode = str.getBytes("UTF-8");
// base64 解密
return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), "UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
byte[] encode = str.getBytes(StandardCharsets.UTF_8);
// base64 解密
return new String(Base64.decode(encode, 0, encode.length, Base64.DEFAULT), StandardCharsets.UTF_8);
}
@NonNull

View file

@ -310,7 +310,7 @@ object MusicUtil : KoinComponent {
context: Context,
playlist: Playlist
): Boolean {
return playlist.name != null && playlist.name == context.getString(R.string.favorites)
return playlist.name == context.getString(R.string.favorites)
}
fun toggleFavorite(context: Context, song: Song) {
@ -349,7 +349,7 @@ object MusicUtil : KoinComponent {
BaseColumns._ID, MediaStore.MediaColumns.DATA
)
// Split the query into multiple batches, and merge the resulting cursors
var batchStart = 0
var batchStart: Int
var batchEnd = 0
val batchSize =
1000000 / 10 // 10^6 being the SQLite limite on the query lenth in bytes, 10 being the max number of digits in an int, used to store the track ID

View file

@ -195,7 +195,7 @@ public class PlaylistsUtil {
final int playlistId = songs.get(0).getPlaylistId();
Uri uri = MediaStore.Audio.Playlists.Members.getContentUri(
"external", playlistId);
String selectionArgs[] = new String[songs.size()];
String[] selectionArgs = new String[songs.size()];
for (int i = 0; i < selectionArgs.length; i++) {
selectionArgs[i] = String.valueOf(songs.get(i).getIdInPlayList());
}
@ -247,7 +247,7 @@ public class PlaylistsUtil {
}
public static File savePlaylist(Context context, Playlist playlist) throws IOException {
return M3UWriter.write(context, new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
return M3UWriter.write(new File(Environment.getExternalStorageDirectory(), "Playlists"), playlist);
}
public static boolean doesPlaylistExist(@NonNull final Context context, final int playlistId) {

View file

@ -93,7 +93,7 @@ object PreferenceUtil {
}
}
val languageCode get() = sharedPreferences.getString(LANGUAGE_NAME, "auto")
val languageCode: String get() = sharedPreferences.getString(LANGUAGE_NAME, "auto") ?: "auto"
var userName
get() = sharedPreferences.getString(
@ -528,7 +528,7 @@ object PreferenceUtil {
get() {
val folderPath = FoldersFragment.getDefaultStartDirectory().path
val filePath: String = sharedPreferences.getStringOrDefault(START_DIRECTORY, folderPath)
return File(filePath) ?: File(FoldersFragment.getDefaultStartDirectory().path)
return File(filePath)
}
set(value) = sharedPreferences.edit {
putString(

View file

@ -70,7 +70,7 @@ public class RetroUtil {
}
public static String formatValue(float value) {
String arr[] = {"", "K", "M", "B", "T", "P", "E"};
String[] arr = {"", "K", "M", "B", "T", "P", "E"};
int index = 0;
while ((value / 1000) >= 1) {
value = value / 1000;

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(android.R.string.ok) { _, _ ->
.setPositiveButton(R.string.ok) { _, _ ->
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:" + context.applicationContext.packageName)
context.startActivity(intent)

View file

@ -36,11 +36,62 @@ public class ImageUtils {
private static final int ALPHA_TOLERANCE = 50;
// Size of the smaller bitmap we're actually going to scan.
private static final int COMPACT_BITMAP_SIZE = 64; // pixels
private final Matrix mTempMatrix = new Matrix();
private int[] mTempBuffer;
private Bitmap mTempCompactBitmap;
private Canvas mTempCompactBitmapCanvas;
private Paint mTempCompactBitmapPaint;
private final Matrix mTempMatrix = new Matrix();
/**
* Classifies a color as grayscale or not. Grayscale here means "very close to a perfect
* gray"; if all three channels are approximately equal, this will return true.
* <p>
* Note that really transparent colors are always grayscale.
*/
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
}
/**
* Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) {
if (drawable == null) {
return null;
}
int originalWidth = drawable.getIntrinsicWidth();
int originalHeight = drawable.getIntrinsicHeight();
if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) &&
(drawable instanceof BitmapDrawable)) {
return ((BitmapDrawable) drawable).getBitmap();
}
if (originalHeight <= 0 || originalWidth <= 0) {
return null;
}
// create a new bitmap, scaling down to fit the max dimensions of
// a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight);
ratio = Math.min(1.0f, ratio);
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
// and paint our app bitmap on it
Canvas canvas = new Canvas(result);
drawable.setBounds(0, 0, scaledWidth, scaledHeight);
drawable.draw(canvas);
return result;
}
/**
* Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect
@ -93,55 +144,4 @@ public class ImageUtils {
mTempBuffer = new int[size];
}
}
/**
* Classifies a color as grayscale or not. Grayscale here means "very close to a perfect
* gray"; if all three channels are approximately equal, this will return true.
* <p>
* Note that really transparent colors are always grayscale.
*/
public static boolean isGrayscale(int color) {
int alpha = 0xFF & (color >> 24);
if (alpha < ALPHA_TOLERANCE) {
return true;
}
int r = 0xFF & (color >> 16);
int g = 0xFF & (color >> 8);
int b = 0xFF & color;
return Math.abs(r - g) < TOLERANCE
&& Math.abs(r - b) < TOLERANCE
&& Math.abs(g - b) < TOLERANCE;
}
/**
* Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight.
*/
public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth,
int maxHeight) {
if (drawable == null) {
return null;
}
int originalWidth = drawable.getIntrinsicWidth();
int originalHeight = drawable.getIntrinsicHeight();
if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) &&
(drawable instanceof BitmapDrawable)) {
return ((BitmapDrawable) drawable).getBitmap();
}
if (originalHeight <= 0 || originalWidth <= 0) {
return null;
}
// create a new bitmap, scaling down to fit the max dimensions of
// a large notification icon if necessary
float ratio = Math.min((float) maxWidth / (float) originalWidth,
(float) maxHeight / (float) originalHeight);
ratio = Math.min(1.0f, ratio);
int scaledWidth = (int) (ratio * originalWidth);
int scaledHeight = (int) (ratio * originalHeight);
Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888);
// and paint our app bitmap on it
Canvas canvas = new Canvas(result);
drawable.setBounds(0, 0, scaledWidth, scaledHeight);
drawable.draw(canvas);
return result;
}
}

View file

@ -0,0 +1,17 @@
package code.name.monkey.retromusic.views
import android.content.Context
import android.content.res.ColorStateList
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
import code.name.monkey.retromusic.extensions.accentColor
class AccentIcon @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = -1
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
imageTintList = ColorStateList.valueOf(accentColor())
}
}

View file

@ -39,7 +39,7 @@ class ColorIconsImageView @JvmOverloads constructor(
val attributes =
context.obtainStyledAttributes(attrs, R.styleable.ColorIconsImageView, 0, 0)
val color =
attributes.getColor(R.styleable.ColorIconsImageView_iconBackgroundColor, Color.RED);
attributes.getColor(R.styleable.ColorIconsImageView_iconBackgroundColor, Color.RED)
setIconBackgroundColor(color)
attributes.recycle()
}

View file

@ -32,7 +32,7 @@ class RetroShapeableImageView @JvmOverloads constructor(
val typedArray =
context.obtainStyledAttributes(attrs, R.styleable.RetroShapeableImageView, defStyle, -1)
val cornerSize =
typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f);
typedArray.getDimension(R.styleable.RetroShapeableImageView_retroCornerSize, 0f)
updateCornerSize(cornerSize)
typedArray.recycle()
}

View file

@ -19,6 +19,6 @@
android:viewportHeight="24">
<path
android:fillColor="@color/md_white_1000"
android:pathData="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22 0.03 -1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z" />
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM7.07,18.28c0.43,-0.9 3.05,-1.78 4.93,-1.78s4.51,0.88 4.93,1.78C15.57,19.36 13.86,20 12,20s-3.57,-0.64 -4.93,-1.72zM18.36,16.83c-1.43,-1.74 -4.9,-2.33 -6.36,-2.33s-4.93,0.59 -6.36,2.33C4.62,15.49 4,13.82 4,12c0,-4.41 3.59,-8 8,-8s8,3.59 8,8c0,1.82 -0.62,3.49 -1.64,4.83zM12,6c-1.94,0 -3.5,1.56 -3.5,3.5S10.06,13 12,13s3.5,-1.56 3.5,-3.5S13.94,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z" />
</vector>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="Pacifico"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -87,7 +87,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="0dp"
android:textAppearance="@style/TextViewHeadline5"
android:textAppearance="@style/TextViewHeadline6"
android:textColor="@color/md_white_1000"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/userImage"

View file

@ -57,7 +57,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="0dp"
android:textAppearance="@style/TextViewHeadline5"
android:textAppearance="@style/TextViewHeadline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/userImage"

View file

@ -12,10 +12,10 @@
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
app:lrcLabel="@string/no_lyrics_found"
app:lrcNormalTextSize="32sp"
app:lrcPadding="16dp"
app:lrcNormalTextSize="24sp"
app:lrcPadding="24dp"
app:lrcTextGravity="left"
app:lrcTextSize="32sp" />
app:lrcTextSize="28sp" />
<com.google.android.material.bottomappbar.BottomAppBar
android:id="@+id/appBarLayout"

View file

@ -8,14 +8,17 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/playAction"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="4dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/action_play_all"
android:textColor="?android:attr/textColorPrimary"
app:backgroundTint="?attr/colorSurface"
app:icon="@drawable/ic_play_arrow"
app:cornerRadius="8dp"
app:layout_constraintEnd_toStartOf="@+id/shuffleAction"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
@ -23,14 +26,17 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/shuffleAction"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/shuffle"
android:textColor="?android:attr/textColorPrimary"
app:backgroundTint="?attr/colorSurface"
app:icon="@drawable/ic_shuffle"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="@+id/playAction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"

View file

@ -91,7 +91,7 @@
app:layout_constraintStart_toEndOf="@id/artistImage"
app:layout_constraintTop_toTopOf="@id/artistImage"
tools:ignore="MissingPrefix"
tools:text="@tools:sample/lorem/random" />
tools:text="@tools:sample/full_names" />
<code.name.monkey.retromusic.views.BaselineGridTextView
android:id="@+id/albumText"
@ -100,7 +100,7 @@
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:singleLine="true"
android:textAppearance="@style/TextViewHeadline6"
android:textAppearance="@style/TextViewSubtitle1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toEndOf="parent"
@ -108,7 +108,7 @@
app:layout_constraintTop_toBottomOf="@id/albumTitle"
app:lineHeightHint="24sp"
tools:ignore="MissingPrefix"
tools:text="@tools:sample/lorem/random" />
tools:text="@tools:sample/full_names" />
<include
layout="@layout/fragment_album_content"

View file

@ -8,13 +8,17 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/playAction"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="4dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/action_play_all"
android:textColor="?android:attr/textColorPrimary"
app:backgroundTint="?attr/colorSurface"
app:icon="@drawable/ic_play_arrow"
app:cornerRadius="8dp"
app:layout_constraintEnd_toStartOf="@+id/shuffleAction"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
@ -22,13 +26,17 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/shuffleAction"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:text="@string/shuffle"
android:textColor="?android:attr/textColorPrimary"
app:backgroundTint="?attr/colorSurface"
app:icon="@drawable/ic_shuffle"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="@+id/playAction"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"

View file

@ -86,7 +86,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textAppearance="@style/TextViewHeadline6"
android:textAppearance="@style/TextViewSubtitle1"
android:textColor="?android:attr/textColorSecondary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -85,7 +85,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="0dp"
android:textAppearance="@style/TextViewHeadline5"
android:textAppearance="@style/TextViewHeadline6"
android:textColor="@color/md_white_1000"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/userImage"

View file

@ -54,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:padding="0dp"
android:textAppearance="@style/TextViewHeadline5"
android:textAppearance="@style/TextViewHeadline6"
android:textColor="?android:attr/textColorPrimary"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/userImage"

Some files were not shown because too many files have changed in this diff Show more