Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.android.swingmusic.auth.presentation.screen.destinations.LoginWithUse
import com.android.swingmusic.common.presentation.navigator.CommonNavigator
import com.android.swingmusic.folder.presentation.screen.destinations.FoldersAndTracksScreenDestination
import com.android.swingmusic.home.presentation.destinations.HomeDestination
import com.android.swingmusic.player.presentation.screen.destinations.LyricsScreenDestination
import com.android.swingmusic.player.presentation.screen.destinations.QueueScreenDestination
import com.android.swingmusic.search.presentation.screen.destinations.ViewAllSearchResultsDestination
import com.ramcosta.composedestinations.navigation.navigate
Expand Down Expand Up @@ -145,4 +146,10 @@ class CoreNavigator(
launchSingleTop = true
}
}

override fun gotoLyrics() {
navController.navigate(LyricsScreenDestination) {
launchSingleTop = true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.android.swingmusic.auth.presentation.screen.destinations.LoginWithQrC
import com.android.swingmusic.auth.presentation.screen.destinations.LoginWithUsernameScreenDestination
import com.android.swingmusic.folder.presentation.screen.destinations.FoldersAndTracksScreenDestination
import com.android.swingmusic.home.presentation.destinations.HomeDestination
import com.android.swingmusic.player.presentation.screen.destinations.LyricsScreenDestination
import com.android.swingmusic.player.presentation.screen.destinations.NowPlayingScreenDestination
import com.android.swingmusic.player.presentation.screen.destinations.QueueScreenDestination
import com.android.swingmusic.search.presentation.screen.destinations.SearchScreenDestination
Expand Down Expand Up @@ -43,6 +44,7 @@ object NavGraphs {
// inner destinations
NowPlayingScreenDestination,
QueueScreenDestination,
LyricsScreenDestination,
AlbumWithInfoScreenDestination,
ViewAllScreenOnArtistDestination,
ArtistInfoScreenDestination,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class LyricsCheckDto(
@SerializedName("exists")
val exists: Boolean?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class LyricsDto(
@SerializedName("error")
val error: Boolean?,
@SerializedName("synced")
val synced: Boolean?,
@SerializedName("lyrics")
val lyrics: List<LyricsLineDto>?,
@SerializedName("copyright")
val copyright: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class LyricsLineDto(
@SerializedName("time")
val time: Double?,
@SerializedName("text")
val text: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class LyricsRequestDto(
@SerializedName("filepath")
val filepath: String,
@SerializedName("trackhash")
val trackhash: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class PluginLyricsRequestDto(
@SerializedName("trackhash")
val trackhash: String,
@SerializedName("title")
val title: String,
@SerializedName("artist")
val artist: String,
@SerializedName("filepath")
val filepath: String,
@SerializedName("album")
val album: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.android.swingmusic.core.data.dto

import com.google.gson.annotations.SerializedName

data class PluginLyricsResultDto(
@SerializedName("trackhash")
val trackhash: String?,
@SerializedName("lyrics")
val lyrics: List<LyricsLineDto>?,
@SerializedName("error")
val error: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import com.android.swingmusic.core.data.dto.FolderDto
import com.android.swingmusic.core.data.dto.FoldersAndTracksDto
import com.android.swingmusic.core.data.dto.FoldersAndTracksRequestDto
import com.android.swingmusic.core.data.dto.GenreDto
import com.android.swingmusic.core.data.dto.LyricsDto
import com.android.swingmusic.core.data.dto.LyricsLineDto
import com.android.swingmusic.core.data.dto.PluginLyricsResultDto
import com.android.swingmusic.core.data.dto.RootDirsDto
import com.android.swingmusic.core.data.dto.TopResultItemDto
import com.android.swingmusic.core.data.dto.TopSearchResultsDto
Expand All @@ -42,6 +45,8 @@ import com.android.swingmusic.core.domain.model.Folder
import com.android.swingmusic.core.domain.model.FoldersAndTracks
import com.android.swingmusic.core.domain.model.FoldersAndTracksRequest
import com.android.swingmusic.core.domain.model.Genre
import com.android.swingmusic.core.domain.model.Lyrics
import com.android.swingmusic.core.domain.model.LyricsLine
import com.android.swingmusic.core.domain.model.RootDirs
import com.android.swingmusic.core.domain.model.TopResultItem
import com.android.swingmusic.core.domain.model.TopSearchResults
Expand Down Expand Up @@ -112,6 +117,34 @@ object Map {
)
}

fun LyricsLineDto.toLyricsLine(): LyricsLine {
return LyricsLine(
time = (time ?: 0.0).toLong(),
text = text ?: ""
)
}

fun LyricsDto.toLyrics(): Lyrics {
val lines = lyrics?.map { it.toLyricsLine() } ?: emptyList()
val hasError = error == true
return Lyrics(
synced = synced ?: true,
lines = if (hasError) emptyList() else lines,
copyright = copyright ?: "",
exists = !hasError && lines.isNotEmpty()
)
}

fun PluginLyricsResultDto.toLyrics(): Lyrics {
val lines = lyrics?.map { it.toLyricsLine() } ?: emptyList()
return Lyrics(
synced = true,
lines = lines,
copyright = "",
exists = lines.isNotEmpty()
)
}

fun FoldersAndTracksRequestDto.toFolderAndTracksRequest(): FoldersAndTracksRequest {
return FoldersAndTracksRequest(
folder = folder ?: "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.android.swingmusic.core.domain.model

data class Lyrics(
val synced: Boolean,
val lines: List<LyricsLine>,
val copyright: String,
val exists: Boolean
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.android.swingmusic.core.domain.model

data class LyricsLine(
val time: Long,
val text: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,6 @@ interface CommonNavigator {

fun gotoSourceFolder(name: String, path: String)

fun gotoLyrics()

}
2 changes: 2 additions & 0 deletions feature/player/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ dependencies {
implementation(project(":uicomponent"))
// Common
implementation(project(":feature:common"))
implementation(project(":feature:settings"))

// Core
implementation(libs.androidx.core.ktx)
Expand All @@ -66,6 +67,7 @@ dependencies {
// Hilt DI
implementation(libs.hilt.android)
ksp(libs.hilt.android.compiler)
implementation(libs.androidx.hilt.navigation.compose)

// Retrofit
implementation(libs.retrofit)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package com.android.swingmusic.player.data.di


import com.android.swingmusic.player.data.repository.DataLyricsRepository
import com.android.swingmusic.player.data.repository.DataPLayerRepository
import com.android.swingmusic.player.domain.repository.LyricsRepository
import com.android.swingmusic.player.domain.repository.PLayerRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -15,4 +18,10 @@ abstract class RepositoryModule {
abstract fun bindQueueRepository(
dataQueueRepository: DataPLayerRepository
): PLayerRepository

@Binds
@Singleton
abstract fun bindLyricsRepository(
dataLyricsRepository: DataLyricsRepository
): LyricsRepository
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.android.swingmusic.player.data.repository

import com.android.swingmusic.auth.data.baseurlholder.BaseUrlHolder
import com.android.swingmusic.auth.data.tokenholder.AuthTokenHolder
import com.android.swingmusic.auth.domain.repository.AuthRepository
import com.android.swingmusic.core.data.dto.LyricsRequestDto
import com.android.swingmusic.core.data.dto.PluginLyricsRequestDto
import com.android.swingmusic.core.data.mapper.Map.toLyrics
import com.android.swingmusic.core.data.util.Resource
import com.android.swingmusic.core.domain.model.Lyrics
import com.android.swingmusic.network.data.api.service.NetworkApiService
import com.android.swingmusic.player.domain.repository.LyricsRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
import javax.inject.Inject

class DataLyricsRepository @Inject constructor(
private val networkApiService: NetworkApiService,
private val authRepository: AuthRepository
) : LyricsRepository {

private suspend fun authHeader(): String {
val token = AuthTokenHolder.accessToken ?: authRepository.getAccessToken()
return "Bearer ${token ?: "TOKEN NOT FOUND"}"
}

private suspend fun baseUrl(): String {
return BaseUrlHolder.baseUrl ?: authRepository.getBaseUrl() ?: ""
}

override suspend fun getLyrics(filepath: String, trackHash: String): Flow<Resource<Lyrics>> =
flow {
try {
emit(Resource.Loading())
val dto = networkApiService.getLyrics(
url = "${baseUrl()}lyrics",
request = LyricsRequestDto(filepath = filepath, trackhash = trackHash),
bearerToken = authHeader()
)
emit(Resource.Success(dto.toLyrics()))
} catch (e: IOException) {
emit(Resource.Error<Lyrics>(message = "Unable to fetch lyrics\nCheck your connection and try again!"))
} catch (e: HttpException) {
emit(Resource.Error<Lyrics>(message = "Unable to fetch lyrics"))
} catch (e: Exception) {
Timber.tag("LYRICS").e(e)
emit(Resource.Error<Lyrics>(message = "Connection Failed"))
}
}

override suspend fun checkLyricsExist(filepath: String, trackHash: String): Boolean {
return try {
val dto = networkApiService.checkLyricsExist(
url = "${baseUrl()}lyrics/check",
request = LyricsRequestDto(filepath = filepath, trackhash = trackHash),
bearerToken = authHeader()
)
dto.exists ?: false
} catch (e: Exception) {
false
}
}

override suspend fun searchLyricsOnline(
trackHash: String,
title: String,
artist: String,
filepath: String,
album: String
): Flow<Resource<Lyrics>> = flow {
try {
emit(Resource.Loading())
val dto = networkApiService.searchLyricsOnline(
url = "${baseUrl()}plugins/lyrics/search",
request = PluginLyricsRequestDto(
trackhash = trackHash,
title = title,
artist = artist,
filepath = filepath,
album = album
),
bearerToken = authHeader()
)
val pluginError = dto.error
if (!pluginError.isNullOrEmpty()) {
emit(Resource.Error<Lyrics>(message = pluginError))
} else {
val lyrics = dto.toLyrics()
if (lyrics.lines.isEmpty()) {
emit(Resource.Error<Lyrics>(message = "No lyrics found"))
} else {
emit(Resource.Success(lyrics.copy(exists = true)))
}
}
} catch (e: IOException) {
emit(Resource.Error<Lyrics>(message = "Unable to search lyrics\nCheck your connection and try again!"))
} catch (e: HttpException) {
emit(Resource.Error<Lyrics>(message = "Unable to search lyrics"))
} catch (e: Exception) {
Timber.tag("LYRICS").e(e)
emit(Resource.Error<Lyrics>(message = "Search failed"))
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.android.swingmusic.player.domain.repository

import com.android.swingmusic.core.data.util.Resource
import com.android.swingmusic.core.domain.model.Lyrics
import kotlinx.coroutines.flow.Flow

interface LyricsRepository {

suspend fun getLyrics(filepath: String, trackHash: String): Flow<Resource<Lyrics>>

suspend fun checkLyricsExist(filepath: String, trackHash: String): Boolean

suspend fun searchLyricsOnline(
trackHash: String,
title: String,
artist: String,
filepath: String,
album: String
): Flow<Resource<Lyrics>>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.android.swingmusic.player.presentation.event

import com.android.swingmusic.core.domain.model.Track

sealed interface LyricsUiEvent {
data class LoadLyrics(val track: Track) : LyricsUiEvent
data class PositionChanged(val positionMs: Long) : LyricsUiEvent
data class SetUserScrolled(val value: Boolean) : LyricsUiEvent
data class SearchOnline(val track: Track) : LyricsUiEvent
}
Loading