Skip to content

Commit

Permalink
Merge branch 'master' into fix/retry-offset
Browse files Browse the repository at this point in the history
  • Loading branch information
Lambada10 authored Oct 24, 2024
2 parents ab5e81e + 290dc89 commit 76f661b
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 31 deletions.
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 @@ -74,6 +77,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 All @@ -96,6 +104,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")
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 Down Expand Up @@ -69,7 +69,7 @@ class LyricsProviderService {
} ?: throw NoTrackFoundException()

Providers.MUSIXMATCH -> MusixmatchAPI().getSongInfo(query, offset).also {
musixmatchID = it?.musixmatchID ?: 0
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 @@ -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 @@ -21,25 +20,32 @@ class MusixmatchAPI {
* @return Search result as a SongInfo object.
*/
suspend fun getSongInfo(query: SongInfo, offset: Int = 0): SongInfo? {
val search = withContext(Dispatchers.IO) {
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 json = json.decodeFromString<MusixmatchSearchResponse>(responseBody)

val result = try {
json[offset]
Expand All @@ -53,24 +59,20 @@ class MusixmatchAPI {
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 @@ -29,6 +29,7 @@ import pl.lambada.songsync.ui.screens.about.components.MultiPersonSwitch
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.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 @@ -89,6 +90,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 @@ -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
28 changes: 28 additions & 0 deletions app/src/main/java/pl/lambada/songsync/util/MiscelaneousUtils.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,43 @@
package pl.lambada.songsync.util

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.widget.Toast
import androidx.core.content.FileProvider
import java.io.File

fun isLegacyFileAccessRequired(filePath: String?): Boolean {
// Before Android 11, not in internal storage
return Build.VERSION.SDK_INT < Build.VERSION_CODES.R
&& filePath?.contains("/storage/emulated/0/") == false
}

fun openFileFromPath(context: Context, filePath: String) {
val file = File(filePath)
if (!file.exists()) {
showToast(context, "File does not exist")
return
}

val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(context, context.packageName + ".provider", file)
} else {
Uri.fromFile(file)
}

val intent = Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, "audio/mp3")
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
Toast.makeText(context, "No app found to open the music file.", Toast.LENGTH_SHORT).show()
}
}

fun showToast(context: Context, messageResId: Int, vararg args: Any, long: Boolean = true) {
Toast
.makeText(
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,6 @@
<string name="embed_non_local_song_error">You tried to embed the lyrics to a non-local file. Aborting operation.</string>
<string name="multi_person_word_by_word">Multi-person word by word lyrics</string>
<string name="multi_person_word_by_word_summary">Use multi-person lyrics format when getting lyrics from Apple Music</string>
<string name="synced_lyrics">Get Synced Lyrics</string>
<string name="synced_lyrics_summary">Toggle to switch between synced and unsynced lyrics from Musixmatch.</string>
</resources>
3 changes: 3 additions & 0 deletions app/src/main/res/xml/file_paths.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<paths>
<external-path name="external_files" path="." />
</paths>

0 comments on commit 76f661b

Please sign in to comment.