WIP Lyrics

This commit is contained in:
Hemanth S 2020-09-06 23:26:39 +05:30
parent 8eb7859f30
commit 26edcdf4da
18 changed files with 228 additions and 45 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 {
@ -72,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,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

@ -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.DeezerService 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

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

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

@ -3,39 +3,17 @@ package code.name.monkey.retromusic.network
import android.content.Context import android.content.Context
import code.name.monkey.retromusic.App import code.name.monkey.retromusic.App
import code.name.monkey.retromusic.BuildConfig import code.name.monkey.retromusic.BuildConfig
import code.name.monkey.retromusic.deezer.DeezerService 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.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.module
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 {
provideDefaultCache()
}
factory {
provideOkHttp(get(), get())
}
single {
provideLastFmRetrofit(get())
}
single {
provideDeezerRest(get())
}
single {
provideLastFmRest(get())
}
}
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) {
@ -95,3 +73,11 @@ fun provideDeezerRest(retrofit: Retrofit): DeezerService {
.build() .build()
return newBuilder.create(DeezerService::class.java) 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,7 @@ 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.Result.* import code.name.monkey.retromusic.network.Result.*
import code.name.monkey.retromusic.network.model.LastFmAlbum import code.name.monkey.retromusic.network.model.LastFmAlbum
@ -94,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(
@ -107,9 +109,16 @@ 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)

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>