diff --git a/.idea/gradle.xml b/.idea/gradle.xml index ae388c2..0897082 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,16 +4,15 @@ diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricStorage.kt b/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricStorage.kt index 84338a4..b117873 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricStorage.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricStorage.kt @@ -11,6 +11,7 @@ import io.github.teccheck.fastlyrics.model.SongWithLyrics import io.github.teccheck.fastlyrics.utils.Utils import java.util.concurrent.Executors import dev.forkhandles.result4k.Result +import io.github.teccheck.fastlyrics.model.LyricsType object LyricStorage { private const val TAG = "LyricsStorage" @@ -73,4 +74,7 @@ object LyricStorage { fun findSong(title: String, artist: String): SongWithLyrics? = database.songsDao().findSong(title, artist) + + fun findSong(title: String, artist: String, type: LyricsType): SongWithLyrics? = + database.songsDao().findSong(title, artist, type) } \ No newline at end of file diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricsApi.kt b/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricsApi.kt index 4934d2a..e20ba87 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricsApi.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/api/LyricsApi.kt @@ -5,9 +5,12 @@ import androidx.lifecycle.MutableLiveData import dev.forkhandles.result4k.Failure import dev.forkhandles.result4k.Result import dev.forkhandles.result4k.Success +import io.github.teccheck.fastlyrics.api.provider.Deezer +import io.github.teccheck.fastlyrics.api.provider.Genius import io.github.teccheck.fastlyrics.api.provider.LyricsProvider import io.github.teccheck.fastlyrics.exceptions.LyricsApiException import io.github.teccheck.fastlyrics.exceptions.LyricsNotFoundException +import io.github.teccheck.fastlyrics.model.LyricsType import io.github.teccheck.fastlyrics.model.SearchResult import io.github.teccheck.fastlyrics.model.SongMeta import io.github.teccheck.fastlyrics.model.SongWithLyrics @@ -17,12 +20,17 @@ object LyricsApi { private const val TAG = "LyricsApi" - private val executor = Executors.newSingleThreadExecutor() + private val executor = Executors.newFixedThreadPool(2) + + private var providers: Array = arrayOf(Genius) + private var providers_synced: Array = arrayOf(Deezer) - private var providers: Array = LyricsProvider.getAllProviders() private val provider: LyricsProvider get() = providers.first() + private val provider_synced: LyricsProvider + get() = providers_synced.first() + fun setProviderOrder(order: Array) { val all = LyricsProvider.getAllProviders() providers = order.mapNotNull { name -> all.find { provider -> provider.getName() == name } } @@ -31,16 +39,20 @@ object LyricsApi { fun getLyricsAsync( songMeta: SongMeta, - liveDataTarget: MutableLiveData> + liveDataTarget: MutableLiveData>, + synced: Boolean = false ) { executor.submit { - val song = songMeta.artist?.let { LyricStorage.findSong(songMeta.title, it) } + Log.d(TAG, "getLyricsAsync($synced)") + + val type = if (synced) LyricsType.LRC else LyricsType.RAW_TEXT + val song = songMeta.artist?.let { LyricStorage.findSong(songMeta.title, it, type) } if (song != null) { liveDataTarget.postValue(Success(song)) return@submit } - val result = fetchLyrics(songMeta) + val result = fetchLyrics(songMeta, synced) liveDataTarget.postValue(result) if (result is Success) { @@ -82,7 +94,8 @@ object LyricsApi { } private fun fetchLyrics( - songMeta: SongMeta + songMeta: SongMeta, + synced: Boolean = false ): Result { var searchQuery = songMeta.title if (songMeta.artist != null) { @@ -92,6 +105,12 @@ object LyricsApi { var bestResult: SearchResult? = null var bestResultScore = 0.0 + val providers = if (synced) { + providers_synced + } else { + providers + } + for (provider in providers) { val search = provider.search(searchQuery) if (search !is Success) diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/api/storage/SongsDao.kt b/app/src/main/java/io/github/teccheck/fastlyrics/api/storage/SongsDao.kt index 744d91a..ca37035 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/api/storage/SongsDao.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/api/storage/SongsDao.kt @@ -3,6 +3,7 @@ package io.github.teccheck.fastlyrics.api.storage import androidx.room.Dao import androidx.room.Insert import androidx.room.Query +import io.github.teccheck.fastlyrics.model.LyricsType import io.github.teccheck.fastlyrics.model.SongWithLyrics @Dao @@ -14,6 +15,10 @@ interface SongsDao { @Query("SELECT * FROM songs WHERE title = :title AND artist = :artist") fun findSong(title: String, artist: String): SongWithLyrics? + + @Query("SELECT * FROM songs WHERE title = :title AND artist = :artist AND type = :type") + fun findSong(title: String, artist: String, type: LyricsType): SongWithLyrics? + @Query("SELECT * FROM songs WHERE id = :id") fun getSong(id: Long): SongWithLyrics? diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsFragment.kt b/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsFragment.kt index 8f1fb8d..8157237 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsFragment.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsFragment.kt @@ -12,6 +12,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.squareup.picasso.Picasso import dev.forkhandles.result4k.Failure import dev.forkhandles.result4k.Success +import dev.forkhandles.result4k.Result import io.github.teccheck.fastlyrics.R import io.github.teccheck.fastlyrics.Settings import io.github.teccheck.fastlyrics.api.provider.LyricsProvider @@ -58,10 +59,23 @@ class FastLyricsFragment : Fragment() { } lyricsViewModel.songWithLyrics.observe(viewLifecycleOwner) { result -> + binding.header.syncedLyricsSwitch.isChecked = false binding.refreshLayout.isRefreshing = false + displayResult(result) + } + + lyricsViewModel.songWithLyricsSynced.observe(viewLifecycleOwner) { result -> + binding.refreshLayout.isRefreshing = false + when (result) { - is Success -> displaySongWithLyrics(result.value) - is Failure -> displayError(result.reason) + is Success -> { + lyricsViewModel.syncedLyricsAvailable = true + binding.header.syncedLyricsAvailable.visibility = View.VISIBLE + } + is Failure -> { + binding.header.syncedLyricsAvailable.visibility = View.GONE + binding.header.syncedLyricsSwitch.isChecked = false + } } } @@ -72,6 +86,16 @@ class FastLyricsFragment : Fragment() { ) binding.refreshLayout.setOnRefreshListener { loadLyricsForCurrentSong() } + binding.header.syncedLyricsSwitch.setOnCheckedChangeListener { _, checked -> + val song = if (checked && lyricsViewModel.syncedLyricsAvailable) { + lyricsViewModel.songWithLyricsSynced.value + } else { + lyricsViewModel.songWithLyrics.value + } + + song?.let { displayResult(it) } + } + val notificationAccess = context?.let { DummyNotificationListenerService.canAccessNotifications(it) } ?: false @@ -104,6 +128,13 @@ class FastLyricsFragment : Fragment() { } } + private fun displayResult(result: Result) { + when (result) { + is Success -> displaySongWithLyrics(result.value) + is Failure -> displayError(result.reason) + } + } + private fun displaySongMeta(songMeta: SongMeta) { binding.header.container.visibility = View.VISIBLE binding.errorView.container.visibility = View.GONE @@ -128,7 +159,12 @@ class FastLyricsFragment : Fragment() { val providerIconRes = Utils.getProviderIconRes(it)!! val icon = AppCompatResources.getDrawable(requireContext(), providerIconRes) binding.lyricsView.source.setIconResource(providerIconRes) - binding.lyricsView.textLyricsProvider.setCompoundDrawablesRelativeWithIntrinsicBounds(icon, null, null, null) + binding.lyricsView.textLyricsProvider.setCompoundDrawablesRelativeWithIntrinsicBounds( + icon, + null, + null, + null + ) val nameRes = Utils.getProviderNameRes(it)!! val name = getString(nameRes) diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsViewModel.kt b/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsViewModel.kt index 9521ba0..2efe49c 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsViewModel.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/ui/fastlyrics/FastLyricsViewModel.kt @@ -20,21 +20,25 @@ class FastLyricsViewModel : ViewModel() { private val _songMeta = MutableLiveData>() private val _songWithLyrics = MutableLiveData>() + private val _songWithLyricsSynced = MutableLiveData>() private val _songPosition = MutableLiveData() private var songPositionTimer: Timer? = null val songMeta: LiveData> = _songMeta val songWithLyrics: LiveData> = _songWithLyrics + val songWithLyricsSynced: LiveData> = _songWithLyricsSynced val songPosition: LiveData = _songPosition + var syncedLyricsAvailable = false var autoRefresh = false private val songMetaCallback = MediaSession.SongMetaCallback { if (!autoRefresh) return@SongMetaCallback _songMeta.postValue(Success(it)) - LyricsApi.getLyricsAsync(it, _songWithLyrics) + loadLyrics(it) + } fun loadLyricsForCurrentSong(context: Context): Boolean { @@ -47,12 +51,18 @@ class FastLyricsViewModel : ViewModel() { _songMeta.value = songMetaResult if (songMetaResult is Success) { - LyricsApi.getLyricsAsync(songMetaResult.value, _songWithLyrics) + loadLyrics(songMetaResult.value) } return songMetaResult is Success } + private fun loadLyrics(songMeta: SongMeta) { + syncedLyricsAvailable = false + LyricsApi.getLyricsAsync(songMeta, _songWithLyrics) + LyricsApi.getLyricsAsync(songMeta, _songWithLyricsSynced, true) + } + fun setupSongMetaListener() { MediaSession.registerSongMetaCallback(songMetaCallback) } diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/ui/saved/RecyclerAdapter.kt b/app/src/main/java/io/github/teccheck/fastlyrics/ui/saved/RecyclerAdapter.kt index 3a0e008..6cf88eb 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/ui/saved/RecyclerAdapter.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/ui/saved/RecyclerAdapter.kt @@ -10,7 +10,9 @@ import androidx.recyclerview.selection.SelectionTracker import androidx.recyclerview.widget.RecyclerView import com.squareup.picasso.Picasso import io.github.teccheck.fastlyrics.R +import io.github.teccheck.fastlyrics.api.provider.LyricsProvider import io.github.teccheck.fastlyrics.model.SongWithLyrics +import io.github.teccheck.fastlyrics.utils.Utils class RecyclerAdapter : RecyclerView.Adapter() { @@ -22,6 +24,7 @@ class RecyclerAdapter : private val imageArt: ImageView private val textTitle: TextView private val textArtist: TextView + private val iconProvider: ImageView private val selectionIcon: ImageView private var song: SongWithLyrics? = null @@ -30,6 +33,7 @@ class RecyclerAdapter : imageArt = view.findViewById(R.id.image_song_art) textTitle = view.findViewById(R.id.text_song_title) textArtist = view.findViewById(R.id.text_song_artist) + iconProvider = view.findViewById(R.id.provider_icon) selectionIcon = view.findViewById(R.id.selection_icon) } @@ -37,6 +41,11 @@ class RecyclerAdapter : this.song = song textTitle.text = song.title textArtist.text = song.artist + + LyricsProvider.getProviderByName(song.provider)?.let { provider -> + Utils.getProviderIconRes(provider)?.let { iconProvider.setImageResource(it) } + } + Picasso.get().load(song.artUrl).into(imageArt) selectionIcon.visibility = if (selected) View.VISIBLE else View.GONE } diff --git a/app/src/main/java/io/github/teccheck/fastlyrics/utils/Utils.kt b/app/src/main/java/io/github/teccheck/fastlyrics/utils/Utils.kt index 4548138..d3bfa3b 100644 --- a/app/src/main/java/io/github/teccheck/fastlyrics/utils/Utils.kt +++ b/app/src/main/java/io/github/teccheck/fastlyrics/utils/Utils.kt @@ -44,7 +44,7 @@ object Utils { return when(provider) { Genius -> R.drawable.genius Deezer -> R.drawable.deezer - else -> null + else -> R.drawable.fastlyrics } } @@ -52,7 +52,7 @@ object Utils { return when(provider) { Genius -> R.string.source_genius Deezer -> R.string.source_deezer - else -> null + else -> R.string.app_name } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/fastlyrics.xml b/app/src/main/res/drawable/fastlyrics.xml new file mode 100644 index 0000000..13aa3c4 --- /dev/null +++ b/app/src/main/res/drawable/fastlyrics.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/layout_header.xml b/app/src/main/res/layout/layout_header.xml index b43281d..67be178 100644 --- a/app/src/main/res/layout/layout_header.xml +++ b/app/src/main/res/layout/layout_header.xml @@ -1,40 +1,70 @@ + - - + android:animateLayoutChanges="true"> + + - + android:layout_gravity="center_vertical" + android:layout_marginStart="16dp" + android:orientation="vertical"> - + + + + + + + + + + android:layout_weight="1" + android:drawableStart="@drawable/fastlyrics" + android:drawablePadding="8dp" + android:drawableTint="?android:attr/colorControlNormal" + android:text="@string/synced_lyrics_available" + app:useMaterialThemeColors="true" /> + diff --git a/app/src/main/res/layout/list_item_song.xml b/app/src/main/res/layout/list_item_song.xml index c2ca48f..0c0a67a 100644 --- a/app/src/main/res/layout/list_item_song.xml +++ b/app/src/main/res/layout/list_item_song.xml @@ -40,9 +40,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 4bd068b..8b25659 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -20,6 +20,8 @@ Es gab ein Netzwerkproblem beim Laden des Textes Unbekannter Fehler + Synchronisierter Songtext verfügbar + Songtext von Kopieren diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bce1252..b1d3651 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,8 @@ There was a network problem while loading lyrics An unknown error occurred + Synced lyrics available + Lyrics from Copy