Skip to content

Commit

Permalink
Merge branch 'master' into feat/songpath
Browse files Browse the repository at this point in the history
  • Loading branch information
Lambada10 authored Oct 24, 2024
2 parents c7b813c + 344d540 commit d6259dc
Show file tree
Hide file tree
Showing 17 changed files with 158 additions and 44 deletions.
3 changes: 2 additions & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
var multiPersonWordByWord by mutableStateOf(dataStore.get(multiPersonWordByWordKey, false))
private set

var syncedMusixmatch by mutableStateOf(dataStore.get(syncedMusixmatchKey, true))
private set

var pureBlack by mutableStateOf(dataStore.get(pureBlackKey, false))
private set

Expand Down Expand Up @@ -77,6 +80,11 @@ class UserSettingsController(private val dataStore: DataStore<Preferences>) {
multiPersonWordByWord = to
}

fun updateSyncedMusixmatch(to: Boolean) {
dataStore.set(syncedMusixmatchKey, to)
syncedMusixmatch = to
}

fun updateDisableMarquee(to: Boolean) {
dataStore.set(disableMarqueeKey, to)
disableMarquee = to
Expand Down Expand Up @@ -104,6 +112,7 @@ private val blacklistedFoldersKey = stringPreferencesKey("blacklist")
private val hideLyricsKey = booleanPreferencesKey("hide_lyrics")
private val includeTranslationKey = booleanPreferencesKey("include_translation")
private val multiPersonWordByWordKey = booleanPreferencesKey("multi_person_word_by_word")
private val syncedMusixmatchKey = booleanPreferencesKey("synced_lyrics")
private val disableMarqueeKey = booleanPreferencesKey("marquee_disable")
private val pureBlackKey = booleanPreferencesKey("pure_black")
private val sdCardPathKey = stringPreferencesKey("sd_card_path")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ class LyricsProviderService {
// Apple Track ID
private var appleID = 0L

// Musixmatch Track ID
private var musixmatchID = 0L
// Musixmatch Song Info
private var musixmatchSongInfo: SongInfo? = null
// TODO: Use values from SongInfo object returned by search instead of storing them here

/**
Expand All @@ -52,11 +52,11 @@ class LyricsProviderService {
UnknownHostException::class, FileNotFoundException::class, NoTrackFoundException::class,
EmptyQueryException::class, InternalErrorException::class
)
suspend fun getSongInfo(query: SongInfo, offset: Int? = 0, provider: Providers): SongInfo? {
suspend fun getSongInfo(query: SongInfo, offset: Int = 0, provider: Providers): SongInfo? {
return try {
when (provider) {
Providers.SPOTIFY -> spotifyAPI.getSongInfo(query, offset)
Providers.LRCLIB -> LRCLibAPI().getSongInfo(query).also {
Providers.LRCLIB -> LRCLibAPI().getSongInfo(query, offset).also {
lrcLibID = it?.lrcLibID ?: 0
} ?: throw NoTrackFoundException()

Expand All @@ -68,8 +68,8 @@ class LyricsProviderService {
appleID = it?.appleID ?: 0
} ?: throw NoTrackFoundException()

Providers.MUSIXMATCH -> MusixmatchAPI().getSongInfo(query).also {
musixmatchID = it?.musixmatchID ?: 0
Providers.MUSIXMATCH -> MusixmatchAPI().getSongInfo(query, offset).also {
musixmatchSongInfo = it
} ?: throw NoTrackFoundException()
}
} catch (e: InternalErrorException) {
Expand All @@ -94,7 +94,8 @@ class LyricsProviderService {
provider: Providers,
// TODO providers could be a sealed interface to include such parameters
includeTranslationNetEase: Boolean = false,
multiPersonWordByWord: Boolean = false
multiPersonWordByWord: Boolean = false,
syncedMusixmatch: Boolean = true
): String? {
return try {
when (provider) {
Expand All @@ -108,7 +109,10 @@ class LyricsProviderService {
appleID,
multiPersonWordByWord
)
Providers.MUSIXMATCH -> MusixmatchAPI().getSyncedLyrics(musixmatchID)
Providers.MUSIXMATCH -> MusixmatchAPI().getLyrics(
musixmatchSongInfo,
syncedMusixmatch
)
}
} catch (e: Exception) {
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class LRCLibAPI {
* @param query The SongInfo object with songName and artistName fields filled.
* @return Search result as a SongInfo object.
*/
suspend fun getSongInfo(query: SongInfo): SongInfo? {
suspend fun getSongInfo(query: SongInfo, offset: Int = 0): SongInfo? {
val search = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName}", // it doesn't work with artist name and song name together
Expand All @@ -41,10 +41,16 @@ class LRCLibAPI {

val json = json.decodeFromString<List<LRCLibResponse>>(responseBody)

val song = try {
json[offset]
} catch (e: IndexOutOfBoundsException) {
return null
}

return SongInfo(
songName = json[0].trackName,
artistName = json[0].artistName,
lrcLibID = json[0].id
songName = song.trackName,
artistName = song.artistName,
lrcLibID = song.id
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import io.ktor.client.statement.bodyAsText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import pl.lambada.songsync.domain.model.SongInfo
import pl.lambada.songsync.domain.model.lyrics_providers.others.MusixmatchLyricsResponse
import pl.lambada.songsync.domain.model.lyrics_providers.others.MusixmatchSearchResponse
import pl.lambada.songsync.util.EmptyQueryException
import pl.lambada.songsync.util.networking.Ktor.client
Expand All @@ -20,53 +19,54 @@ class MusixmatchAPI {
* @param query The SongInfo object with songName and artistName fields filled.
* @return Search result as a SongInfo object.
*/
suspend fun getSongInfo(query: SongInfo): SongInfo? {
val search = withContext(Dispatchers.IO) {
suspend fun getSongInfo(query: SongInfo, offset: Int = 0): SongInfo? {
val artistName = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName} ${query.artistName}",
"${query.artistName}",
Charsets.UTF_8.toString()
)
}

if (search == " ")
val songName = withContext(Dispatchers.IO) {
URLEncoder.encode(
"${query.songName}",
Charsets.UTF_8.toString()
)
}

if (artistName == "" || songName == "")
throw EmptyQueryException()

val response = client.get(
"$baseURL/search?q=$search"
"$baseURL/full?artist=$artistName&track=$songName"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299 || responseBody == "[]")
if (response.status.value !in 200..299)
return null

val json = json.decodeFromString<List<MusixmatchSearchResponse>>(responseBody)

val result = json[0]
val result = json.decodeFromString<MusixmatchSearchResponse>(responseBody)

return SongInfo(
songName = result.songName,
artistName = result.artistName,
songLink = result.url,
albumCoverLink = result.artwork,
musixmatchID = result.id,
hasSyncedLyrics = result.hasSyncedLyrics,
hasUnsyncedLyrics = result.hasSyncedLyrics,
syncedLyrics = result.syncedLyrics?.lyrics,
unsyncedLyrics = result.unsyncedLyrics?.lyrics
)
}

/**
* Searches for synced lyrics using the song name and artist name.
* @param id The ID of the song from search results.
* @return The synced lyrics as a string.
* Returns the lyrics.
* @param songInfo The SongInfo of the song from search results.
* @return The lyrics as a string or null if the lyrics were not found.
*/
suspend fun getSyncedLyrics(id: Long): String? {
val response = client.get(
"$baseURL/lyrics?id=$id"
)
val responseBody = response.bodyAsText(Charsets.UTF_8)

if (response.status.value !in 200..299)
return null

val json = json.decodeFromString<MusixmatchLyricsResponse>(responseBody)
return json.lyrics
fun getLyrics(songInfo: SongInfo?, synced: Boolean = true): String? {
return if(synced) songInfo?.syncedLyrics
else songInfo?.unsyncedLyrics
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ data class SongInfo(
var neteaseID: Long? = null, // Netease-only
var appleID: Long? = null, // Apple-only
var musixmatchID: Long? = null, // Musixmatch-only
var hasSyncedLyrics: Boolean? = null, // Musixmatch-only
var hasUnsyncedLyrics: Boolean? = null, // Musixmatch-only
var syncedLyrics: String? = null, // Musixmatch-only
var unsyncedLyrics: String? = null, // Musixmatch-only
) : Parcelable
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,24 @@ data class MusixmatchSearchResponse(
val url: String,
val albumId: Long,
val hasSyncedLyrics: Boolean,
val hasUnsyncedLyrics: Boolean
val hasUnsyncedLyrics: Boolean,
val syncedLyrics: SyncedLyricsResponse? = null,
val unsyncedLyrics: UnsyncedLyricsResponse? = null
)

@Serializable
data class MusixmatchLyricsResponse(
data class SyncedLyricsResponse(
val id: Long,
val duration: Int,
val language: String,
val updatedTime: String,
val lyrics: String
)

@Serializable
data class UnsyncedLyricsResponse(
val id: Long,
val language: String,
val updatedTime: String,
val lyrics: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
Expand All @@ -28,6 +29,7 @@ import coil.compose.rememberAsyncImagePainter
import coil.imageLoader
import coil.request.ImageRequest
import pl.lambada.songsync.R
import pl.lambada.songsync.util.openFileFromPath

@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
Expand All @@ -41,11 +43,16 @@ fun SharedTransitionScope.SongCard(
animatedVisibilityScope: AnimatedVisibilityScope,
) {
val unknownArtistString = stringResource(R.string.unknown)
val context = LocalContext.current

OutlinedCard(
shape = RoundedCornerShape(10.dp),
modifier = CombinedModifier(
outer = Modifier.fillMaxWidth(),
outer = Modifier
.fillMaxWidth()
.clickable(filePath != null) {
openFileFromPath(context, filePath!!)
},
inner = modifier
)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import pl.lambada.songsync.ui.screens.about.components.PureBlackThemeSwitch
import pl.lambada.songsync.ui.screens.about.components.SdCardPathSetting
import pl.lambada.songsync.ui.screens.about.components.ShowPathSwitch
import pl.lambada.songsync.ui.screens.about.components.SupportSection
import pl.lambada.songsync.ui.screens.about.components.SyncedLyricsSwitch
import pl.lambada.songsync.ui.screens.about.components.TranslationSwitch
import pl.lambada.songsync.ui.screens.about.components.UpdateAvailableDialog
import pl.lambada.songsync.util.ext.getVersion
Expand Down Expand Up @@ -97,6 +98,13 @@ fun AboutScreen(
)
}

item {
SyncedLyricsSwitch(
selected = userSettingsController.syncedMusixmatch,
onToggle = { userSettingsController.updateSyncedMusixmatch(it) }
)
}

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
item {
SdCardPathSetting(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pl.lambada.songsync.ui.screens.about.components

import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import pl.lambada.songsync.R
import pl.lambada.songsync.ui.components.AboutItem
import pl.lambada.songsync.ui.components.SwitchItem

@Composable
fun SyncedLyricsSwitch(selected: Boolean, onToggle: (Boolean) -> Unit) {
AboutItem(label = stringResource(id = R.string.synced_lyrics)) {
SwitchItem(
label = stringResource(id = R.string.synced_lyrics_summary),
selected = selected,
onClick = { onToggle(!selected) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ class HomeViewModel(
version,
provider = userSettingsController.selectedProvider,
includeTranslationNetEase = userSettingsController.includeTranslation,
multiPersonWordByWord = userSettingsController.multiPersonWordByWord
multiPersonWordByWord = userSettingsController.multiPersonWordByWord,
syncedMusixmatch = userSettingsController.syncedMusixmatch
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,9 @@ fun SharedTransitionScope.LyricsFetchScreen(
lyricsFetchState = viewModel.lyricsFetchState,
animatedVisibilityScope = animatedVisibilityScope,
disableMarquee = viewModel.userSettingsController.disableMarquee,
allowTryingAgain = viewModel.userSettingsController.selectedProvider != Providers.LRCLIB
&& viewModel.userSettingsController.selectedProvider != Providers.APPLE
allowTryingAgain =
viewModel.userSettingsController.selectedProvider != Providers.APPLE &&
viewModel.userSettingsController.selectedProvider != Providers.MUSIXMATCH
)

is QueryStatus.Failed -> FailedDialogue(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ class LyricsFetchViewModel(
version,
userSettingsController.selectedProvider,
userSettingsController.includeTranslation,
userSettingsController.multiPersonWordByWord
userSettingsController.multiPersonWordByWord,
userSettingsController.syncedMusixmatch
)

fun loadSongInfo(context: Context, tryingAgain: Boolean = false) {
Expand Down
Loading

0 comments on commit d6259dc

Please sign in to comment.