Merge branch 'room-playlist' of https://github.com/h4h13/RetroMusicPlayer into room-playlist
This commit is contained in:
commit
416ee91c39
28 changed files with 451 additions and 225 deletions
|
@ -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'
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
@ -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 {
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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>>
|
||||||
|
}
|
|
@ -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>>
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
})
|
})
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
@ -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)
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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(
|
||||||
|
|
|
@ -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 =
|
||||||
|
|
|
@ -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"
|
||||||
|
|
33
app/src/main/res/layout/lyrics_dialog.xml
Normal file
33
app/src/main/res/layout/lyrics_dialog.xml
Normal 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>
|
|
@ -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>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue