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

main
Hemanth S 2020-09-07 13:34:54 +05:30
commit 416ee91c39
28 changed files with 451 additions and 225 deletions

View File

@ -161,7 +161,6 @@ dependencies {
implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0' implementation 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:1.0.0'
implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r' implementation 'org.eclipse.mylyn.github:org.eclipse.egit.github.core:3.4.0.201406110918-r'
implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0' implementation 'com.github.ksoichiro:android-observablescrollview:1.6.0'
implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod' implementation 'com.github.kabouzeid:recyclerview-fastscroll:1.9-kmod'
implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3' implementation 'com.github.AdrienPoupa:jaudiotagger:2.2.3'
@ -169,7 +168,7 @@ dependencies {
implementation 'com.r0adkll:slidableactivity:2.1.0' implementation 'com.r0adkll:slidableactivity:2.1.0'
implementation 'com.heinrichreimersoftware:material-intro:1.6' implementation 'com.heinrichreimersoftware:material-intro:1.6'
implementation 'com.github.dhaval2404:imagepicker:1.7.1' implementation 'com.github.dhaval2404:imagepicker:1.7.1'
implementation 'org.jsoup:jsoup:1.11.1'
implementation 'me.zhanghai.android.fastscroll:library:1.1.0' implementation 'me.zhanghai.android.fastscroll:library:1.1.0'
implementation 'me.jorgecastillo:androidcolorx:0.2.0' implementation 'me.jorgecastillo:androidcolorx:0.2.0'

View File

@ -60,20 +60,23 @@
</style> </style>
<style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton"> <style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textSize">16sp</item> <item name="android:textAppearance">@style/TextViewButton</item>
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
<item name="android:padding">0dp</item>
</style> </style>
<style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text"> <style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:textSize">14sp</item> <item name="android:textAppearance">@style/TextViewBody1</item>
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
<item name="android:paddingTop">16dp</item>
</style> </style>
<style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text"> <style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
<item name="android:textSize">20sp</item> <item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
<item name="fontFamily">@font/sans</item> <item name="fontFamily">@font/sans</item>
<item name="android:padding">16dp</item>
</style> </style>
</resources> </resources>

View File

@ -14,7 +14,7 @@ import code.name.monkey.retromusic.fragments.genres.GenreDetailsViewModel
import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel import code.name.monkey.retromusic.fragments.playlists.PlaylistDetailsViewModel
import code.name.monkey.retromusic.fragments.search.SearchViewModel import code.name.monkey.retromusic.fragments.search.SearchViewModel
import code.name.monkey.retromusic.model.Genre import code.name.monkey.retromusic.model.Genre
import code.name.monkey.retromusic.network.networkModule import code.name.monkey.retromusic.network.*
import code.name.monkey.retromusic.repository.* import code.name.monkey.retromusic.repository.*
import code.name.monkey.retromusic.util.FilePathUtil import code.name.monkey.retromusic.util.FilePathUtil
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
@ -25,6 +25,28 @@ import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.bind import org.koin.dsl.bind
import org.koin.dsl.module import org.koin.dsl.module
val networkModule = module {
factory {
provideDefaultCache()
}
factory {
provideOkHttp(get(), get())
}
single {
provideLastFmRetrofit(get())
}
single {
provideDeezerRest(get())
}
single {
provideLastFmRest(get())
}
single {
provideLyrics(get())
}
}
private val roomModule = module { private val roomModule = module {
single { single {
@ -56,8 +78,12 @@ private val roomModule = module {
get<RetroDatabase>().playCountDao() get<RetroDatabase>().playCountDao()
} }
factory {
get<RetroDatabase>().historyDao()
}
single { single {
RealRoomRepository(get(), get(), get()) RealRoomRepository(get(), get(), get(), get())
} bind RoomRepository::class } bind RoomRepository::class
} }
private val mainModule = module { private val mainModule = module {
@ -68,7 +94,20 @@ private val mainModule = module {
} }
private val dataModule = module { private val dataModule = module {
single { single {
RealRepository(get(), get(), get(), get(), get(), get(), get(), get(), get(), get(), get()) RealRepository(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
} bind Repository::class } bind Repository::class
single { single {

View File

@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.dialogs.LyricsDialog
import code.name.monkey.retromusic.fragments.AlbumCoverStyle import code.name.monkey.retromusic.fragments.AlbumCoverStyle
import code.name.monkey.retromusic.fragments.NowPlayingScreen.* import code.name.monkey.retromusic.fragments.NowPlayingScreen.*
import code.name.monkey.retromusic.glide.RetroMusicColoredTarget import code.name.monkey.retromusic.glide.RetroMusicColoredTarget
@ -90,14 +91,15 @@ class AlbumCoverPagerAdapter(
val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false) val view = inflater.inflate(getLayoutWithPlayerTheme(), container, false)
albumCover = view.findViewById(R.id.player_image) albumCover = view.findViewById(R.id.player_image)
albumCover.setOnClickListener { albumCover.setOnClickListener {
showLyricsDialog() LyricsDialog().show(childFragmentManager, "LyricsDialog")
//showLyricsDialog()
} }
return view return view
} }
private fun showLyricsDialog() { private fun showLyricsDialog() {
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val data = MusicUtil.getLyrics(song) val data: String = MusicUtil.getLyrics(song) ?: "No lyrics found"
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder( MaterialAlertDialogBuilder(
requireContext(), requireContext(),

View File

@ -0,0 +1,26 @@
package code.name.monkey.retromusic.db
import androidx.lifecycle.LiveData
import androidx.room.*
@Dao
interface HistoryDao {
companion object {
private const val HISTORY_LIMIT = 100
}
@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 LIMIT $HISTORY_LIMIT")
fun historySongs(): List<HistoryEntity>
@Query("SELECT * FROM HistoryEntity ORDER BY time_played DESC LIMIT $HISTORY_LIMIT")
fun observableHistorySongs(): LiveData<List<HistoryEntity>>
}

View File

@ -45,17 +45,6 @@ interface PlaylistDao {
@Delete @Delete
suspend fun deleteSongsInPlaylist(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") @Query("SELECT * FROM SongEntity WHERE playlist_creator_id= :playlistId")
fun favoritesSongsLiveData(playlistId: Int): LiveData<List<SongEntity>> fun favoritesSongsLiveData(playlistId: Int): LiveData<List<SongEntity>>

View File

@ -12,4 +12,5 @@ abstract class RetroDatabase : RoomDatabase() {
abstract fun playlistDao(): PlaylistDao abstract fun playlistDao(): PlaylistDao
abstract fun blackListStore(): BlackListStoreDao abstract fun blackListStore(): BlackListStoreDao
abstract fun playCountDao(): PlayCountDao abstract fun playCountDao(): PlayCountDao
abstract fun historyDao(): HistoryDao
} }

View File

@ -0,0 +1,51 @@
package code.name.monkey.retromusic.dialogs
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.repository.Repository
import kotlinx.android.synthetic.main.lyrics_dialog.*
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.koin.android.ext.android.inject
class LyricsDialog : DialogFragment() {
override fun getTheme(): Int {
return R.style.MaterialAlertDialogTheme
}
private val repository by inject<Repository>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.lyrics_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch(IO) {
val result: Result<String> = repository.lyrics(
MusicPlayerRemote.currentSong.artistName,
MusicPlayerRemote.currentSong.title
)
withContext(Main) {
when (result) {
is Result.Error -> println("Error")
is Result.Loading -> println("Loading")
is Result.Success -> lyricsText.text = result.data
}
}
}
}
}

View File

@ -8,7 +8,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
fun DialogFragment.materialDialog(title: Int): MaterialAlertDialogBuilder { fun DialogFragment.materialDialog(title: Int): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder( return MaterialAlertDialogBuilder(
requireContext(), requireContext(),
R.style.ThemeOverlay_MaterialComponents_Dialog_Alert R.style.MaterialAlertDialogTheme
).setTitle(title) ).setTitle(title)
} }

View File

@ -104,7 +104,7 @@ class DetailListFragment : AbsMainActivityFragment(R.layout.fragment_playlist_de
adapter = songAdapter adapter = songAdapter
layoutManager = linearLayoutManager() layoutManager = linearLayoutManager()
} }
repository.historySong().observe(viewLifecycleOwner, Observer { repository.observableHistorySongs().observe(viewLifecycleOwner, Observer {
val songs = it.map { historyEntity -> historyEntity.toSong() } val songs = it.map { historyEntity -> historyEntity.toSong() }
songAdapter.swapDataSet(songs) songAdapter.swapDataSet(songs)
}) })

View File

@ -38,6 +38,7 @@ import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.helper.SortOrder import code.name.monkey.retromusic.helper.SortOrder
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
@ -83,15 +84,7 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
startPostponedEnterTransition() startPostponedEnterTransition()
showAlbum(it) showAlbum(it)
}) })
detailsViewModel.getArtist().observe(viewLifecycleOwner, Observer {
loadArtistImage(it)
})
detailsViewModel.getMoreAlbums().observe(viewLifecycleOwner, Observer {
moreAlbums(it)
})
detailsViewModel.getAlbumInfo().observe(viewLifecycleOwner, Observer {
aboutAlbum(it)
})
setupRecyclerView() setupRecyclerView()
artistImage.setOnClickListener { artistImage.setOnClickListener {
requireActivity().findNavController(R.id.fragment_container) requireActivity().findNavController(R.id.fragment_container)
@ -172,8 +165,23 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
loadAlbumCover(album) loadAlbumCover(album)
simpleSongAdapter.swapDataSet(album.songs) simpleSongAdapter.swapDataSet(album.songs)
detailsViewModel.loadArtist(album.artistId) detailsViewModel.getArtist(album.artistId).observe(viewLifecycleOwner, Observer {
detailsViewModel.loadAlbumInfo(album) loadArtistImage(it)
})
detailsViewModel.getAlbumInfo(album).observe(viewLifecycleOwner, Observer { result ->
when (result) {
is Result.Loading -> {
println("Loading")
}
is Result.Error -> {
println("Error")
}
is Result.Success -> {
aboutAlbum(result.data)
}
}
})
} }
private fun moreAlbums(albums: List<Album>) { private fun moreAlbums(albums: List<Album>) {
@ -214,6 +222,9 @@ class AlbumDetailsFragment : AbsMainActivityFragment(R.layout.fragment_album_det
} }
private fun loadArtistImage(artist: Artist) { private fun loadArtistImage(artist: Artist) {
detailsViewModel.getMoreAlbums(artist).observe(viewLifecycleOwner, Observer {
moreAlbums(it)
})
ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist) ArtistGlideRequest.Builder.from(Glide.with(requireContext()), artist)
.forceDownload(PreferenceUtil.isAllowedToDownloadMetadata()) .forceDownload(PreferenceUtil.isAllowedToDownloadMetadata())
.generatePalette(requireContext()) .generatePalette(requireContext())

View File

@ -1,70 +1,44 @@
package code.name.monkey.retromusic.fragments.albums package code.name.monkey.retromusic.fragments.albums
import androidx.lifecycle.* import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Album import code.name.monkey.retromusic.model.Album
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class AlbumDetailsViewModel( class AlbumDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private val albumId: Int private val albumId: Int
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val _album = MutableLiveData<Album>()
private val _artist = MutableLiveData<Artist>()
private val _lastFmAlbum = MutableLiveData<LastFmAlbum>()
private val _moreAlbums = MutableLiveData<List<Album>>()
fun getAlbum(): LiveData<Album> = liveData(IO) { fun getAlbum(): LiveData<Album> = liveData(IO) {
val album = realRepository.albumByIdAsync(albumId) val album = realRepository.albumByIdAsync(albumId)
emit(album) emit(album)
} }
fun getArtist(): LiveData<Artist> = _artist fun getArtist(artistId: Int): LiveData<Artist> = liveData(IO) {
fun getAlbumInfo(): LiveData<LastFmAlbum> = _lastFmAlbum
fun getMoreAlbums(): LiveData<List<Album>> = _moreAlbums
init {
loadAlbumDetails()
}
fun getAlbum2() = liveData(context = viewModelScope.coroutineContext + IO) {
val album = realRepository.albumByIdAsync(albumId)
emit(album)
}
private fun loadAlbumDetails() = viewModelScope.launch(IO) {
val album = loadAlbumAsync.await() ?: throw NullPointerException("Album couldn't found")
_album.postValue(album)
}
fun loadAlbumInfo(album: Album) = viewModelScope.launch(IO) {
val lastFmAlbum = realRepository.albumInfo(album.artistName ?: "-", album.title ?: "-")
_lastFmAlbum.postValue(lastFmAlbum)
}
fun loadArtist(artistId: Int) = viewModelScope.launch(IO) {
val artist = realRepository.artistById(artistId) val artist = realRepository.artistById(artistId)
_artist.postValue(artist) emit(artist)
artist.albums?.filter { item -> item.id != albumId }?.let { albums ->
if (albums.isNotEmpty()) _moreAlbums.postValue(albums)
}
} }
private val loadAlbumAsync: Deferred<Album?> fun getAlbumInfo(album: Album): LiveData<Result<LastFmAlbum>> = liveData {
get() = viewModelScope.async(IO) { emit(Result.Loading)
realRepository.albumByIdAsync(albumId) emit( realRepository.albumInfo(album.artistName ?: "-", album.title ?: "-"))
}
fun getMoreAlbums(artist: Artist): LiveData<List<Album>> = liveData(IO) {
artist.albums?.filter { item -> item.id != albumId }?.let { albums ->
if (albums.isNotEmpty()) emit(albums)
} }
}
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadAlbumDetails()
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View File

@ -18,6 +18,7 @@ import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import code.name.monkey.appthemehelper.ThemeStore import code.name.monkey.appthemehelper.ThemeStore
import code.name.monkey.retromusic.EXTRA_ALBUM_ID
import code.name.monkey.retromusic.R import code.name.monkey.retromusic.R
import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter import code.name.monkey.retromusic.adapter.album.HorizontalAlbumAdapter
import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter import code.name.monkey.retromusic.adapter.song.SimpleSongAdapter
@ -32,6 +33,7 @@ import code.name.monkey.retromusic.glide.ArtistGlideRequest
import code.name.monkey.retromusic.glide.SingleColorTarget import code.name.monkey.retromusic.glide.SingleColorTarget
import code.name.monkey.retromusic.helper.MusicPlayerRemote import code.name.monkey.retromusic.helper.MusicPlayerRemote
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import code.name.monkey.retromusic.util.CustomArtistImageUtil import code.name.monkey.retromusic.util.CustomArtistImageUtil
@ -77,9 +79,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
showArtist(it) showArtist(it)
startPostponedEnterTransition() startPostponedEnterTransition()
}) })
detailsViewModel.getArtistInfo().observe(viewLifecycleOwner, Observer {
artistInfo(it)
})
playAction.apply { playAction.apply {
setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) } setOnClickListener { MusicPlayerRemote.openQueue(artist.songs, 0, true) }
@ -140,6 +140,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
albumTitle.text = albumText albumTitle.text = albumText
songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber }) songAdapter.swapDataSet(artist.songs.sortedBy { it.trackNumber })
artist.albums?.let { albumAdapter.swapDataSet(it) } artist.albums?.let { albumAdapter.swapDataSet(it) }
} }
private fun loadBiography( private fun loadBiography(
@ -148,7 +149,14 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
) { ) {
biography = null biography = null
this.lang = lang this.lang = lang
detailsViewModel.loadBiography(name, lang, null) detailsViewModel.getArtistInfo(name, lang, null)
.observe(viewLifecycleOwner, Observer { result ->
when (result) {
is Result.Loading -> println("Loading")
is Result.Error -> println("Error")
is Result.Success -> artistInfo(result.data)
}
})
} }
private fun artistInfo(lastFmArtist: LastFmArtist?) { private fun artistInfo(lastFmArtist: LastFmArtist?) {
@ -201,7 +209,7 @@ class ArtistDetailsFragment : AbsMainActivityFragment(R.layout.fragment_artist_d
override fun onAlbumClick(albumId: Int, view: View) { override fun onAlbumClick(albumId: Int, view: View) {
findNavController().navigate( findNavController().navigate(
R.id.albumDetailsFragment, R.id.albumDetailsFragment,
bundleOf("extra_album_id" to albumId), bundleOf(EXTRA_ALBUM_ID to albumId),
null, null,
FragmentNavigatorExtras( FragmentNavigatorExtras(
view to getString(R.string.transition_album_art) view to getString(R.string.transition_album_art)

View File

@ -1,51 +1,37 @@
package code.name.monkey.retromusic.fragments.artists package code.name.monkey.retromusic.fragments.artists
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.liveData
import code.name.monkey.retromusic.interfaces.MusicServiceEventListener import code.name.monkey.retromusic.interfaces.MusicServiceEventListener
import code.name.monkey.retromusic.model.Artist import code.name.monkey.retromusic.model.Artist
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.network.model.LastFmArtist
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
class ArtistDetailsViewModel( class ArtistDetailsViewModel(
private val realRepository: RealRepository, private val realRepository: RealRepository,
private val artistId: Int private val artistId: Int
) : ViewModel(), MusicServiceEventListener { ) : ViewModel(), MusicServiceEventListener {
private val loadArtistDetailsAsync: Deferred<Artist?> fun getArtist(): LiveData<Artist> = liveData(IO) {
get() = viewModelScope.async(Dispatchers.IO) { val artist = realRepository.artistById(artistId)
realRepository.artistById(artistId) emit(artist)
}
private val _artist = MutableLiveData<Artist>()
private val _lastFmArtist = MutableLiveData<LastFmArtist>()
fun getArtist(): LiveData<Artist> = _artist
fun getArtistInfo(): LiveData<LastFmArtist> = _lastFmArtist
init {
loadArtistDetails()
} }
private fun loadArtistDetails() = viewModelScope.launch { fun getArtistInfo(
val artist = name: String,
loadArtistDetailsAsync.await() ?: throw NullPointerException("Album couldn't found") lang: String?,
_artist.postValue(artist) cache: String?
} ): LiveData<Result<LastFmArtist>> = liveData(IO) {
emit(Result.Loading)
fun loadBiography(name: String, lang: String?, cache: String?) = viewModelScope.launch {
val info = realRepository.artistInfo(name, lang, cache) val info = realRepository.artistInfo(name, lang, cache)
_lastFmArtist.postValue(info) emit(info)
} }
override fun onMediaStoreChanged() { override fun onMediaStoreChanged() {
loadArtistDetails() getArtist()
} }
override fun onServiceConnected() {} override fun onServiceConnected() {}

View File

@ -6,9 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import code.name.monkey.retromusic.repository.RealRepository import code.name.monkey.retromusic.repository.RealRepository
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class SearchViewModel(private val realRepository: RealRepository) : ViewModel() { class SearchViewModel(private val realRepository: RealRepository) : ViewModel() {
private val results = MutableLiveData<MutableList<Any>>() private val results = MutableLiveData<MutableList<Any>>()
@ -17,6 +15,6 @@ class SearchViewModel(private val realRepository: RealRepository) : ViewModel()
fun search(query: String?) = viewModelScope.launch(IO) { fun search(query: String?) = viewModelScope.launch(IO) {
val result = realRepository.search(query) val result = realRepository.search(query)
withContext(Main) { results.postValue(result) } results.value = result
} }
} }

View File

@ -15,8 +15,8 @@
package code.name.monkey.retromusic.glide.artistimage package code.name.monkey.retromusic.glide.artistimage
import android.content.Context import android.content.Context
import code.name.monkey.retromusic.deezer.Data import code.name.monkey.retromusic.model.Data
import code.name.monkey.retromusic.deezer.DeezerApiService import code.name.monkey.retromusic.network.DeezerService
import code.name.monkey.retromusic.util.MusicUtil import code.name.monkey.retromusic.util.MusicUtil
import code.name.monkey.retromusic.util.PreferenceUtil import code.name.monkey.retromusic.util.PreferenceUtil
import com.bumptech.glide.Priority import com.bumptech.glide.Priority
@ -38,7 +38,7 @@ class ArtistImage(val artistName: String)
class ArtistImageFetcher( class ArtistImageFetcher(
private val context: Context, private val context: Context,
private val deezerApiService: DeezerApiService, private val deezerService: DeezerService,
val model: ArtistImage, val model: ArtistImage,
val urlLoader: ModelLoader<GlideUrl, InputStream>, val urlLoader: ModelLoader<GlideUrl, InputStream>,
val width: Int, val width: Int,
@ -66,7 +66,7 @@ class ArtistImageFetcher(
PreferenceUtil.isAllowedToDownloadMetadata() PreferenceUtil.isAllowedToDownloadMetadata()
) { ) {
val artists = model.artistName.split(",") val artists = model.artistName.split(",")
val response = deezerApiService.getArtistImage(artists[0]).execute() val response = deezerService.getArtistImage(artists[0]).execute()
if (!response.isSuccessful) { if (!response.isSuccessful) {
throw IOException("Request failed with code: " + response.code()) throw IOException("Request failed with code: " + response.code())
@ -106,7 +106,7 @@ class ArtistImageFetcher(
class ArtistImageLoader( class ArtistImageLoader(
val context: Context, val context: Context,
private val deezerApiService: DeezerApiService, private val deezerService: DeezerService,
private val urlLoader: ModelLoader<GlideUrl, InputStream> private val urlLoader: ModelLoader<GlideUrl, InputStream>
) : StreamModelLoader<ArtistImage> { ) : StreamModelLoader<ArtistImage> {
@ -115,7 +115,7 @@ class ArtistImageLoader(
width: Int, width: Int,
height: Int height: Int
): DataFetcher<InputStream> { ): DataFetcher<InputStream> {
return ArtistImageFetcher(context, deezerApiService, model, urlLoader, width, height) return ArtistImageFetcher(context, deezerService, model, urlLoader, width, height)
} }
} }
@ -123,7 +123,7 @@ class Factory(
val context: Context val context: Context
) : ModelLoaderFactory<ArtistImage, InputStream> { ) : ModelLoaderFactory<ArtistImage, InputStream> {
private var deezerApiService: DeezerApiService private var deezerService: DeezerService
private var okHttpFactory: OkHttpUrlLoader.Factory private var okHttpFactory: OkHttpUrlLoader.Factory
init { init {
@ -134,8 +134,8 @@ class Factory(
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.build() .build()
) )
deezerApiService = DeezerApiService.invoke( deezerService = DeezerService.invoke(
DeezerApiService.createDefaultOkHttpClient(context) DeezerService.createDefaultOkHttpClient(context)
.connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .readTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS) .writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS)
@ -156,7 +156,7 @@ class Factory(
): ModelLoader<ArtistImage, InputStream> { ): ModelLoader<ArtistImage, InputStream> {
return ArtistImageLoader( return ArtistImageLoader(
context!!, context!!,
deezerApiService, deezerService,
okHttpFactory.build(context, factories) okHttpFactory.build(context, factories)
) )
} }

View File

@ -1,4 +1,4 @@
package code.name.monkey.retromusic.deezer package code.name.monkey.retromusic.model
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName

View File

@ -1,6 +1,7 @@
package code.name.monkey.retromusic.deezer package code.name.monkey.retromusic.network
import android.content.Context import android.content.Context
import code.name.monkey.retromusic.model.DeezerResponse
import okhttp3.Cache import okhttp3.Cache
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -16,7 +17,7 @@ import java.util.*
private const val BASE_QUERY_ARTIST = "search/artist" private const val BASE_QUERY_ARTIST = "search/artist"
private const val BASE_URL = "https://api.deezer.com/" private const val BASE_URL = "https://api.deezer.com/"
interface DeezerApiService { interface DeezerService {
@GET("$BASE_QUERY_ARTIST&limit=1") @GET("$BASE_QUERY_ARTIST&limit=1")
fun getArtistImage( fun getArtistImage(
@ -26,7 +27,7 @@ interface DeezerApiService {
companion object { companion object {
operator fun invoke( operator fun invoke(
client: okhttp3.Call.Factory client: okhttp3.Call.Factory
): DeezerApiService { ): DeezerService {
return Retrofit.Builder() return Retrofit.Builder()
.baseUrl(BASE_URL) .baseUrl(BASE_URL)
.callFactory(client) .callFactory(client)

View File

@ -0,0 +1,12 @@
package code.name.monkey.retromusic.network
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.Query
interface LyricsRestService {
@Headers("Cache-Control: public")
@GET("/lyrics")
suspend fun getLyrics(@Query("artist") artist: String, @Query("title") title: String): String
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package code.name.monkey.retromusic package code.name.monkey.retromusic.network
/** /**
* Generic class that holds the network state * Generic class that holds the network state

View File

@ -1,67 +1,19 @@
package code.name.monkey.retromusic.network package code.name.monkey.retromusic.network
import android.content.Context
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.Constants.AUDIO_SCROBBLER_URL import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.network.conversion.LyricsConverterFactory
import com.google.gson.Gson import com.google.gson.Gson
import okhttp3.Cache import okhttp3.Cache
import okhttp3.ConnectionPool
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.koin.dsl.module import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.gson.GsonConverterFactory
import java.io.File import java.io.File
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
private const val TIMEOUT: Long = 700
val networkModule = module {
factory {
provideHttpClient(get(), get())
}
factory {
provideCacheControlInterceptor()
}
factory {
provideDefaultCache()
}
factory {
provideLastFmService(get())
}
single {
providerRetrofit(get())
}
}
fun provideLastFmService(retrofit: Retrofit): LastFMService =
retrofit.create(LastFMService::class.java)
fun providerRetrofit(okHttpClient: OkHttpClient.Builder): Retrofit = Retrofit.Builder()
.baseUrl(AUDIO_SCROBBLER_URL)
.callFactory(okHttpClient.build())
.addConverterFactory(GsonConverterFactory.create(Gson()))
.build()
fun provideHttpClient(
cache: Cache,
interceptor: Interceptor
): OkHttpClient.Builder = OkHttpClient.Builder()
.connectionPool(ConnectionPool(0, 1, TimeUnit.NANOSECONDS))
.retryOnConnectionFailure(true)
.connectTimeout(TIMEOUT, TimeUnit.MINUTES)
.writeTimeout(TIMEOUT, TimeUnit.MINUTES)
.readTimeout(TIMEOUT, TimeUnit.MINUTES)
.cache(cache)
.addInterceptor(interceptor)
fun provideCacheControlInterceptor(): Interceptor = Interceptor { chain: Interceptor.Chain ->
val modifiedRequest = chain.request().newBuilder()
.addHeader("Cache-Control", "max-age=31536000, max-stale=31536000")
.build()
chain.proceed(modifiedRequest)
}
fun provideDefaultCache(): Cache? { fun provideDefaultCache(): Cache? {
val cacheDir = File(App.getContext().cacheDir.absolutePath, "/okhttp-lastfm/") val cacheDir = File(App.getContext().cacheDir.absolutePath, "/okhttp-lastfm/")
if (cacheDir.mkdirs() || cacheDir.isDirectory) { if (cacheDir.mkdirs() || cacheDir.isDirectory) {
@ -69,3 +21,63 @@ fun provideDefaultCache(): Cache? {
} }
return null return null
} }
fun logInterceptor(): Interceptor {
val loggingInterceptor = HttpLoggingInterceptor()
if (BuildConfig.DEBUG) {
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
} else {
// disable retrofit log on release
loggingInterceptor.level = HttpLoggingInterceptor.Level.NONE
}
return loggingInterceptor
}
fun headerInterceptor(context: Context): Interceptor {
return Interceptor {
val original = it.request()
val request = original.newBuilder()
.header("User-Agent", context.packageName)
.addHeader("Content-Type", "application/json; charset=utf-8")
.method(original.method(), original.body())
.build()
it.proceed(request)
}
}
fun provideOkHttp(context: Context, cache: Cache): OkHttpClient {
return OkHttpClient.Builder()
.addNetworkInterceptor(logInterceptor())
.addInterceptor(headerInterceptor(context))
.connectTimeout(1, TimeUnit.SECONDS)
.readTimeout(1, TimeUnit.SECONDS)
.cache(cache)
.build()
}
fun provideLastFmRetrofit(client: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://ws.audioscrobbler.com/2.0/")
.addConverterFactory(GsonConverterFactory.create(Gson()))
.callFactory { request -> client.newCall(request) }
.build()
}
fun provideLastFmRest(retrofit: Retrofit): LastFMService {
return retrofit.create(LastFMService::class.java)
}
fun provideDeezerRest(retrofit: Retrofit): DeezerService {
val newBuilder = retrofit.newBuilder()
.baseUrl("https://api.deezer.com/")
.build()
return newBuilder.create(DeezerService::class.java)
}
fun provideLyrics(retrofit: Retrofit): LyricsRestService {
val newBuilder = retrofit.newBuilder()
.baseUrl("https://makeitpersonal.co")
.addConverterFactory(LyricsConverterFactory())
.build()
return newBuilder.create(LyricsRestService::class.java)
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (c) 2019 Naman Dwivedi.
*
* Licensed under the GNU General Public License v3
*
* This is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
*/
package code.name.monkey.retromusic.network.conversion
import okhttp3.MediaType
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Retrofit
import java.lang.reflect.Type
class LyricsConverterFactory : Converter.Factory() {
override fun responseBodyConverter(
type: Type?,
annotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<ResponseBody, *>? {
return if (String::class.java == type) {
Converter<ResponseBody, String> { value -> value.string() }
} else null
}
override fun requestBodyConverter(
type: Type?,
parameterAnnotations: Array<Annotation>?,
methodAnnotations: Array<Annotation>?,
retrofit: Retrofit?
): Converter<*, RequestBody>? {
return if (String::class.java == type) {
Converter<String, RequestBody> { value -> RequestBody.create(MEDIA_TYPE, value) }
} else null
}
companion object {
private val MEDIA_TYPE = MediaType.parse("text/plain")
}
}

View File

@ -21,6 +21,9 @@ import code.name.monkey.retromusic.db.*
import code.name.monkey.retromusic.model.* import code.name.monkey.retromusic.model.*
import code.name.monkey.retromusic.model.smartplaylist.NotPlayedPlaylist import code.name.monkey.retromusic.model.smartplaylist.NotPlayedPlaylist
import code.name.monkey.retromusic.network.LastFMService import code.name.monkey.retromusic.network.LastFMService
import code.name.monkey.retromusic.network.LyricsRestService
import code.name.monkey.retromusic.network.Result
import code.name.monkey.retromusic.network.Result.*
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
import code.name.monkey.retromusic.network.model.LastFmArtist import code.name.monkey.retromusic.network.model.LastFmArtist
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -35,11 +38,12 @@ interface Repository {
fun artistsFlow(): Flow<Result<List<Artist>>> fun artistsFlow(): Flow<Result<List<Artist>>>
fun playlistsFlow(): Flow<Result<List<Playlist>>> fun playlistsFlow(): Flow<Result<List<Playlist>>>
fun genresFlow(): Flow<Result<List<Genre>>> fun genresFlow(): Flow<Result<List<Genre>>>
fun historySong(): LiveData<List<HistoryEntity>> fun historySong(): List<HistoryEntity>
fun favorites(): LiveData<List<SongEntity>> fun favorites(): LiveData<List<SongEntity>>
fun observableHistorySongs(): LiveData<List<HistoryEntity>>
fun albumById(albumId: Int): Album
suspend fun fetchAlbums(): List<Album> suspend fun fetchAlbums(): List<Album>
suspend fun albumByIdAsync(albumId: Int): Album suspend fun albumByIdAsync(albumId: Int): Album
fun albumById(albumId: Int): Album
suspend fun allSongs(): List<Song> suspend fun allSongs(): List<Song>
suspend fun fetchArtists(): List<Artist> suspend fun fetchArtists(): List<Artist>
suspend fun albumArtists(): List<Artist> suspend fun albumArtists(): List<Artist>
@ -48,8 +52,8 @@ interface Repository {
suspend fun search(query: String?): MutableList<Any> suspend fun search(query: String?): MutableList<Any>
suspend fun getPlaylistSongs(playlist: Playlist): List<Song> suspend fun getPlaylistSongs(playlist: Playlist): List<Song>
suspend fun getGenre(genreId: Int): List<Song> suspend fun getGenre(genreId: Int): List<Song>
suspend fun artistInfo(name: String, lang: String?, cache: String?): LastFmArtist suspend fun artistInfo(name: String, lang: String?, cache: String?): Result<LastFmArtist>
suspend fun albumInfo(artist: String, album: String): LastFmAlbum suspend fun albumInfo(artist: String, album: String): Result<LastFmAlbum>
suspend fun artistById(artistId: Int): Artist suspend fun artistById(artistId: Int): Artist
suspend fun recentArtists(): List<Artist> suspend fun recentArtists(): List<Artist>
suspend fun topArtists(): List<Artist> suspend fun topArtists(): List<Artist>
@ -91,6 +95,7 @@ interface Repository {
suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity> suspend fun checkSongExistInPlayCount(songId: Int): List<PlayCountEntity>
suspend fun playCountSongs(): List<PlayCountEntity> suspend fun playCountSongs(): List<PlayCountEntity>
suspend fun blackListPaths(): List<BlackListStoreEntity> suspend fun blackListPaths(): List<BlackListStoreEntity>
suspend fun lyrics(artist: String, title: String): Result<String>
} }
class RealRepository( class RealRepository(
@ -104,13 +109,22 @@ class RealRepository(
private val playlistRepository: PlaylistRepository, private val playlistRepository: PlaylistRepository,
private val searchRepository: RealSearchRepository, private val searchRepository: RealSearchRepository,
private val topPlayedRepository: TopPlayedRepository, private val topPlayedRepository: TopPlayedRepository,
private val roomRepository: RoomRepository private val roomRepository: RoomRepository,
private val lyricsRestService: LyricsRestService
) : Repository { ) : Repository {
override suspend fun lyrics(artist: String, title: String): Result<String> = try {
Success(lyricsRestService.getLyrics(artist, title))
} catch (e: Exception) {
Error
}
override suspend fun fetchAlbums(): List<Album> = albumRepository.albums() override suspend fun fetchAlbums(): List<Album> = albumRepository.albums()
override suspend fun albumByIdAsync(albumId: Int): Album = albumRepository.album(albumId) override suspend fun albumByIdAsync(albumId: Int): Album = albumRepository.album(albumId)
override fun albumById(albumId: Int): Album = albumRepository.album(albumId) override fun albumById(albumId: Int): Album = albumRepository.album(albumId)
override suspend fun fetchArtists(): List<Artist> = artistRepository.artists() override suspend fun fetchArtists(): List<Artist> = artistRepository.artists()
override suspend fun albumArtists(): List<Artist> = artistRepository.albumArtists() override suspend fun albumArtists(): List<Artist> = artistRepository.albumArtists()
@ -147,17 +161,31 @@ class RealRepository(
name: String, name: String,
lang: String?, lang: String?,
cache: String? cache: String?
): LastFmArtist = lastFMService.artistInfo(name, lang, cache) ): Result<LastFmArtist> {
return try {
Success(lastFMService.artistInfo(name, lang, cache))
} catch (e: Exception) {
println(e)
Error
}
}
override suspend fun albumInfo( override suspend fun albumInfo(
artist: String, artist: String,
album: String album: String
): LastFmAlbum = lastFMService.albumInfo(artist, album) ): Result<LastFmAlbum> {
return try {
val lastFmAlbum = lastFMService.albumInfo(artist, album)
Success(lastFmAlbum)
} catch (e: Exception) {
println(e)
Error
}
}
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override suspend fun homeSectionsFlow(): Flow<Result<List<Home>>> { override suspend fun homeSectionsFlow(): Flow<Result<List<Home>>> {
val homes = MutableStateFlow<Result<List<Home>>>(value = Result.Loading) val homes = MutableStateFlow<Result<List<Home>>>(value = Loading)
println("homeSections:Loading")
val homeSections = mutableListOf<Home>() val homeSections = mutableListOf<Home>()
val sections = listOf( val sections = listOf(
topArtistsHome(), topArtistsHome(),
@ -175,9 +203,9 @@ class RealRepository(
} }
} }
if (homeSections.isEmpty()) { if (homeSections.isEmpty()) {
homes.value = Result.Error homes.value = Error
} else { } else {
homes.value = Result.Success(homeSections) homes.value = Success(homeSections)
} }
return homes return homes
} }
@ -279,7 +307,10 @@ class RealRepository(
override suspend fun blackListPaths(): List<BlackListStoreEntity> = override suspend fun blackListPaths(): List<BlackListStoreEntity> =
roomRepository.blackListPaths() roomRepository.blackListPaths()
override fun historySong(): LiveData<List<HistoryEntity>> = override fun observableHistorySongs(): LiveData<List<HistoryEntity>> =
roomRepository.observableHistorySongs()
override fun historySong(): List<HistoryEntity> =
roomRepository.historySongs() roomRepository.historySongs()
override fun favorites(): LiveData<List<SongEntity>> = override fun favorites(): LiveData<List<SongEntity>> =
@ -331,52 +362,52 @@ class RealRepository(
} }
override fun songsFlow(): Flow<Result<List<Song>>> = flow { override fun songsFlow(): Flow<Result<List<Song>>> = flow {
emit(Result.Loading) emit(Loading)
val data = songRepository.songs() val data = songRepository.songs()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Error)
} else { } else {
emit(Result.Success(data)) emit(Success(data))
} }
} }
override fun albumsFlow(): Flow<Result<List<Album>>> = flow { override fun albumsFlow(): Flow<Result<List<Album>>> = flow {
emit(Result.Loading) emit(Loading)
val data = albumRepository.albums() val data = albumRepository.albums()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Error)
} else { } else {
emit(Result.Success(data)) emit(Success(data))
} }
} }
override fun artistsFlow(): Flow<Result<List<Artist>>> = flow { override fun artistsFlow(): Flow<Result<List<Artist>>> = flow {
emit(Result.Loading) emit(Loading)
val data = artistRepository.artists() val data = artistRepository.artists()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Error)
} else { } else {
emit(Result.Success(data)) emit(Success(data))
} }
} }
override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow { override fun playlistsFlow(): Flow<Result<List<Playlist>>> = flow {
emit(Result.Loading) emit(Loading)
val data = playlistRepository.playlists() val data = playlistRepository.playlists()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Error)
} else { } else {
emit(Result.Success(data)) emit(Success(data))
} }
} }
override fun genresFlow(): Flow<Result<List<Genre>>> = flow { override fun genresFlow(): Flow<Result<List<Genre>>> = flow {
emit(Result.Loading) emit(Loading)
val data = genreRepository.genres() val data = genreRepository.genres()
if (data.isEmpty()) { if (data.isEmpty()) {
emit(Result.Error) emit(Error)
} else { } else {
emit(Result.Success(data)) emit(Success(data))
} }
} }
} }

View File

@ -7,9 +7,10 @@ import code.name.monkey.retromusic.model.Song
interface RoomRepository { interface RoomRepository {
fun historySongs(): LiveData<List<HistoryEntity>> fun historySongs(): List<HistoryEntity>
fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>>
fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity) fun insertBlacklistPath(blackListStoreEntity: BlackListStoreEntity)
fun observableHistorySongs(): LiveData<List<HistoryEntity>>
suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long
suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity> suspend fun checkPlaylistExists(playlistName: String): List<PlaylistEntity>
suspend fun playlists(): List<PlaylistEntity> suspend fun playlists(): List<PlaylistEntity>
@ -42,7 +43,8 @@ interface RoomRepository {
class RealRoomRepository( class RealRoomRepository(
private val playlistDao: PlaylistDao, private val playlistDao: PlaylistDao,
private val blackListStoreDao: BlackListStoreDao, private val blackListStoreDao: BlackListStoreDao,
private val playCountDao: PlayCountDao private val playCountDao: PlayCountDao,
private val historyDao: HistoryDao
) : RoomRepository { ) : RoomRepository {
@WorkerThread @WorkerThread
override suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long = override suspend fun createPlaylist(playlistEntity: PlaylistEntity): Long =
@ -59,12 +61,6 @@ class RealRoomRepository(
override suspend fun playlistWithSongs(): List<PlaylistWithSongs> = override suspend fun playlistWithSongs(): List<PlaylistWithSongs> =
playlistDao.playlistsWithSongs() playlistDao.playlistsWithSongs()
/* val tempList = ArrayList<SongEntity>(songs)
val existingSongs = songs.map {
playlistDao.checkSongExistsWithPlaylistName(it.playlistCreatorName, it.songId)
}.first()
println("Existing ${existingSongs.size}")
tempList.removeAll(existingSongs)*/
@WorkerThread @WorkerThread
override suspend fun insertSongs(songs: List<SongEntity>) = override suspend fun insertSongs(songs: List<SongEntity>) =
playlistDao.insertSongsToPlaylist(songs) playlistDao.insertSongsToPlaylist(songs)
@ -96,7 +92,6 @@ class RealRoomRepository(
} }
} }
override suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity> = override suspend fun isFavoriteSong(songEntity: SongEntity): List<SongEntity> =
playlistDao.isSongExistsInPlaylist( playlistDao.isSongExistsInPlaylist(
songEntity.playlistCreatorId, songEntity.playlistCreatorId,
@ -107,17 +102,18 @@ class RealRoomRepository(
playlistDao.removeSongFromPlaylist(songEntity.playlistCreatorId, songEntity.id) playlistDao.removeSongFromPlaylist(songEntity.playlistCreatorId, songEntity.id)
override suspend fun addSongToHistory(currentSong: Song) = override suspend fun addSongToHistory(currentSong: Song) =
playlistDao.insertSongInHistory(currentSong.toHistoryEntity(System.currentTimeMillis())) historyDao.insertSongInHistory(currentSong.toHistoryEntity(System.currentTimeMillis()))
override suspend fun songPresentInHistory(song: Song): HistoryEntity? = override suspend fun songPresentInHistory(song: Song): HistoryEntity? =
playlistDao.isSongPresentInHistory(song.id) historyDao.isSongPresentInHistory(song.id)
override suspend fun updateHistorySong(song: Song) = override suspend fun updateHistorySong(song: Song) =
playlistDao.updateHistorySong(song.toHistoryEntity(System.currentTimeMillis())) historyDao.updateHistorySong(song.toHistoryEntity(System.currentTimeMillis()))
override fun historySongs(): LiveData<List<HistoryEntity>> { override fun observableHistorySongs(): LiveData<List<HistoryEntity>> =
return playlistDao.historySongs() historyDao.observableHistorySongs()
}
override fun historySongs(): List<HistoryEntity> = historyDao.historySongs()
override fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> = override fun favoritePlaylistLiveData(favorite: String): LiveData<List<SongEntity>> =
playlistDao.favoritesSongsLiveData( playlistDao.favoritesSongsLiveData(

View File

@ -111,7 +111,7 @@ object MusicUtil : KoinComponent {
} }
fun getLyrics(song: Song): String? { fun getLyrics(song: Song): String? {
var lyrics: String? = null var lyrics: String? = "No lyrics found"
val file = File(song.data) val file = File(song.data)
try { try {
lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS) lyrics = AudioFileIO.read(file).tagOrCreateDefault.getFirst(FieldKey.LYRICS)
@ -151,7 +151,7 @@ object MusicUtil : KoinComponent {
} }
false false
} }
if (files != null && files.size > 0) { if (files != null && files.isNotEmpty()) {
for (f in files) { for (f in files) {
try { try {
val newLyrics = val newLyrics =

View File

@ -32,6 +32,7 @@
android:scaleType="centerCrop" android:scaleType="centerCrop"
app:civ_border="false" app:civ_border="false"
app:civ_shadow="false" app:civ_shadow="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:retroCornerSize="21dp" app:retroCornerSize="21dp"

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/lyricsText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextViewBody1"
tools:text="@tools:sample/lorem/random" />
</ScrollView>
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -215,14 +215,16 @@
<style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton"> <style name="AppTextAppearance.MaterialAlertDialog.Button" parent="Widget.MaterialComponents.Button.TextButton">
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
<item name="android:padding">0dp</item>
</style> </style>
<style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text"> <style name="AppTextAppearance.MaterialAlertDialog.Body" parent="MaterialAlertDialog.MaterialComponents.Body.Text">
<item name="android:textSize">14sp</item> <item name="android:textAppearance">@style/TextViewBody1</item>
<item name="android:paddingTop">16dp</item>
</style> </style>
<style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text"> <style name="AppTextAppearance.MaterialAlertDialog.Title" parent="MaterialAlertDialog.MaterialComponents.Title.Text">
<item name="android:textSize">20sp</item> <item name="android:textAppearance">@style/TextViewHeadline6</item>
<item name="android:textStyle">bold</item> <item name="android:textStyle">bold</item>
</style> </style>