refactor: move subtitle package to standalone data module, update dependencies
This commit is contained in:
@@ -0,0 +1,53 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
|
alias(libs.plugins.androidMultiplatformLibrary)
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlin {
|
||||||
|
iosArm64()
|
||||||
|
iosSimulatorArm64()
|
||||||
|
|
||||||
|
jvm()
|
||||||
|
|
||||||
|
js {
|
||||||
|
browser()
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalWasmDsl::class)
|
||||||
|
wasmJs {
|
||||||
|
browser()
|
||||||
|
}
|
||||||
|
|
||||||
|
androidLibrary {
|
||||||
|
namespace = "fr.ajaury.gwenedeg.subtitle"
|
||||||
|
compileSdk =
|
||||||
|
libs.versions.androidCompileSdk
|
||||||
|
.get()
|
||||||
|
.toInt()
|
||||||
|
minSdk =
|
||||||
|
libs.versions.androidMinSdk
|
||||||
|
.get()
|
||||||
|
.toInt()
|
||||||
|
|
||||||
|
compilerOptions {
|
||||||
|
jvmTarget = JvmTarget.JVM_11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
commonMain.dependencies {
|
||||||
|
// Logging
|
||||||
|
implementation(projects.core.logging)
|
||||||
|
|
||||||
|
// DI
|
||||||
|
implementation(project.dependencies.platform(libs.koin.bom))
|
||||||
|
implementation(libs.koin.core)
|
||||||
|
}
|
||||||
|
commonTest.dependencies {
|
||||||
|
implementation(libs.kotlin.test)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -9,7 +9,7 @@ import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine
|
|||||||
* Only numeric timestamp tags `[mm:ss.xx]` / `[mm:ss.xxx]` are kept. A line may carry several
|
* Only numeric timestamp tags `[mm:ss.xx]` / `[mm:ss.xxx]` are kept. A line may carry several
|
||||||
* leading timestamps that all share the same text.
|
* leading timestamps that all share the same text.
|
||||||
*/
|
*/
|
||||||
class LrcParser {
|
internal class LrcParser {
|
||||||
private val timeTag = Regex("""\[(\d{1,2}):(\d{2})[.:](\d{2,3})]""")
|
private val timeTag = Regex("""\[(\d{1,2}):(\d{2})[.:](\d{2,3})]""")
|
||||||
private val leadingTimeTags = Regex("""^(?:\[\d{1,2}:\d{2}[.:]\d{2,3}])+""")
|
private val leadingTimeTags = Regex("""^(?:\[\d{1,2}:\d{2}[.:]\d{2,3}])+""")
|
||||||
|
|
||||||
+4
-5
@@ -4,17 +4,16 @@ import fr.ajaury.gwenedeg.core.logging.domain.Logger
|
|||||||
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
|
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
||||||
import fr.ajaury.gwenedeg.subtitle.model.CantGetSubtitleException
|
import fr.ajaury.gwenedeg.subtitle.model.CantGetSubtitleException
|
||||||
import gwenedeg.shared.generated.resources.Res
|
|
||||||
|
|
||||||
class LrcSubtitleRepository(
|
internal class LrcSubtitleRepository(
|
||||||
private val parser: LrcParser,
|
private val parser: LrcParser,
|
||||||
private val logger: Logger,
|
private val logger: Logger,
|
||||||
) : SubtitleRepository {
|
) : SubtitleRepository {
|
||||||
override suspend fun getSubtitle(resourcePath: String): Subtitle =
|
override suspend fun getSubtitle(content: String): Subtitle =
|
||||||
try {
|
try {
|
||||||
parser.parse(Res.readBytes(resourcePath).decodeToString())
|
parser.parse(content = content)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
val message = "Failed to load subtitle: $resourcePath"
|
val message = "Failed to load subtitle: $content"
|
||||||
logger.error(message = message, throwable = exception)
|
logger.error(message = message, throwable = exception)
|
||||||
throw CantGetSubtitleException(message)
|
throw CantGetSubtitleException(message)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package fr.ajaury.gwenedeg.subtitle.di
|
||||||
|
|
||||||
|
import fr.ajaury.gwenedeg.subtitle.data.LrcParser
|
||||||
|
import fr.ajaury.gwenedeg.subtitle.data.LrcSubtitleRepository
|
||||||
|
import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase
|
||||||
|
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
||||||
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.module.dsl.bind
|
||||||
|
import org.koin.core.module.dsl.factoryOf
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val subtitleModule: Module = module {
|
||||||
|
factoryOf(::LrcParser)
|
||||||
|
factoryOf(::LrcSubtitleRepository) { bind<SubtitleRepository>() }
|
||||||
|
factoryOf(::GetCurrentSubtitleIndexUseCase)
|
||||||
|
}
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
package fr.ajaury.gwenedeg.subtitle.domain
|
package fr.ajaury.gwenedeg.subtitle.domain
|
||||||
|
|
||||||
interface SubtitleRepository {
|
interface SubtitleRepository {
|
||||||
suspend fun getSubtitle(resourcePath: String): Subtitle
|
suspend fun getSubtitle(content: String): Subtitle
|
||||||
}
|
}
|
||||||
@@ -40,3 +40,6 @@ include(":shared")
|
|||||||
// Core modules
|
// Core modules
|
||||||
include(":core:audioplayer")
|
include(":core:audioplayer")
|
||||||
include(":core:logging")
|
include(":core:logging")
|
||||||
|
|
||||||
|
// Data modules
|
||||||
|
include(":data:subtitle")
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ kotlin {
|
|||||||
implementation(projects.core.audioplayer)
|
implementation(projects.core.audioplayer)
|
||||||
implementation(projects.core.logging)
|
implementation(projects.core.logging)
|
||||||
|
|
||||||
|
// Data modules
|
||||||
|
implementation(projects.data.subtitle)
|
||||||
|
|
||||||
implementation(libs.compose.runtime)
|
implementation(libs.compose.runtime)
|
||||||
implementation(libs.compose.foundation)
|
implementation(libs.compose.foundation)
|
||||||
implementation(libs.compose.material3)
|
implementation(libs.compose.material3)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import fr.ajaury.gwenedeg.navigation.Route
|
|||||||
import fr.ajaury.gwenedeg.player.di.audioPlayerModule
|
import fr.ajaury.gwenedeg.player.di.audioPlayerModule
|
||||||
import fr.ajaury.gwenedeg.player.ui.PlayerScreen
|
import fr.ajaury.gwenedeg.player.ui.PlayerScreen
|
||||||
import fr.ajaury.gwenedeg.records.ui.RecordsScreen
|
import fr.ajaury.gwenedeg.records.ui.RecordsScreen
|
||||||
|
import fr.ajaury.gwenedeg.subtitle.di.subtitleModule
|
||||||
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
import fr.ajaury.gwenedeg.theme.GwenedegTheme
|
||||||
import org.koin.compose.KoinApplication
|
import org.koin.compose.KoinApplication
|
||||||
import org.koin.dsl.koinConfiguration
|
import org.koin.dsl.koinConfiguration
|
||||||
@@ -25,7 +26,8 @@ fun App() {
|
|||||||
modules(
|
modules(
|
||||||
sharedModule,
|
sharedModule,
|
||||||
audioPlayerModule,
|
audioPlayerModule,
|
||||||
loggingModule
|
loggingModule,
|
||||||
|
subtitleModule
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -4,10 +4,8 @@ import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
|
|||||||
import fr.ajaury.gwenedeg.records.data.InMemoryRecordRepository
|
import fr.ajaury.gwenedeg.records.data.InMemoryRecordRepository
|
||||||
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
import fr.ajaury.gwenedeg.records.domain.RecordRepository
|
||||||
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
|
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
|
||||||
import fr.ajaury.gwenedeg.subtitle.data.LrcParser
|
import fr.ajaury.gwenedeg.resourcereader.data.ComposeResourceReader
|
||||||
import fr.ajaury.gwenedeg.subtitle.data.LrcSubtitleRepository
|
import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase
|
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
|
||||||
import org.koin.core.module.dsl.bind
|
import org.koin.core.module.dsl.bind
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.dsl.factoryOf
|
||||||
import org.koin.core.module.dsl.viewModelOf
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
@@ -15,9 +13,7 @@ import org.koin.dsl.module
|
|||||||
|
|
||||||
val sharedModule = module {
|
val sharedModule = module {
|
||||||
factoryOf(::InMemoryRecordRepository) { bind<RecordRepository>() }
|
factoryOf(::InMemoryRecordRepository) { bind<RecordRepository>() }
|
||||||
factoryOf(::LrcParser)
|
|
||||||
factoryOf(::LrcSubtitleRepository) { bind<SubtitleRepository>() }
|
|
||||||
factoryOf(::GetCurrentSubtitleIndexUseCase)
|
|
||||||
viewModelOf(::RecordsViewModel)
|
viewModelOf(::RecordsViewModel)
|
||||||
viewModelOf(::PlayerViewModel)
|
viewModelOf(::PlayerViewModel)
|
||||||
|
factoryOf(::ComposeResourceReader) { bind<ResourceReader>() }
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-1
@@ -6,6 +6,7 @@ import fr.ajaury.gwenedeg.core.logging.domain.Logger
|
|||||||
import fr.ajaury.gwenedeg.player.domain.AudioPlayer
|
import fr.ajaury.gwenedeg.player.domain.AudioPlayer
|
||||||
import fr.ajaury.gwenedeg.player.domain.AudioSessionManager
|
import fr.ajaury.gwenedeg.player.domain.AudioSessionManager
|
||||||
import fr.ajaury.gwenedeg.player.model.PlaybackState
|
import fr.ajaury.gwenedeg.player.model.PlaybackState
|
||||||
|
import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase
|
import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
|
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
|
||||||
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
|
||||||
@@ -26,6 +27,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
|||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
class PlayerViewModel(
|
class PlayerViewModel(
|
||||||
|
private val resourceReader: ResourceReader,
|
||||||
private val audioPlayer: AudioPlayer,
|
private val audioPlayer: AudioPlayer,
|
||||||
private val audioSessionManager: AudioSessionManager,
|
private val audioSessionManager: AudioSessionManager,
|
||||||
private val subtitleRepository: SubtitleRepository,
|
private val subtitleRepository: SubtitleRepository,
|
||||||
@@ -92,7 +94,8 @@ class PlayerViewModel(
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
subtitle.value =
|
subtitle.value =
|
||||||
try {
|
try {
|
||||||
subtitleRepository.getSubtitle(resourcePath)
|
val content = resourceReader.read(resourcePath = resourcePath)
|
||||||
|
subtitleRepository.getSubtitle(content = content)
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
logger.error(
|
logger.error(
|
||||||
message = "Failed to load subtitle: $resourcePath",
|
message = "Failed to load subtitle: $resourcePath",
|
||||||
|
|||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package fr.ajaury.gwenedeg.resourcereader.data
|
||||||
|
|
||||||
|
import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader
|
||||||
|
import gwenedeg.shared.generated.resources.Res
|
||||||
|
|
||||||
|
internal class ComposeResourceReader : ResourceReader {
|
||||||
|
override suspend fun read(resourcePath: String): String = Res.readBytes(resourcePath).decodeToString()
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
package fr.ajaury.gwenedeg.resourcereader.data.domain
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads the raw text of a subtitle file. Implemented by the host app, which owns the resource
|
||||||
|
* bundle, so this module stays free of any platform/resource dependency.
|
||||||
|
*/
|
||||||
|
interface ResourceReader {
|
||||||
|
suspend fun read(resourcePath: String): String
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user