refactor: move subtitle package to standalone data module, update dependencies

This commit is contained in:
2026-06-19 18:12:16 +02:00
parent fdeeee9bf8
commit 1028b748a9
18 changed files with 108 additions and 16 deletions
+53
View File
@@ -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)
}
}
}
@@ -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
* 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 leadingTimeTags = Regex("""^(?:\[\d{1,2}:\d{2}[.:]\d{2,3}])+""")
@@ -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.SubtitleRepository
import fr.ajaury.gwenedeg.subtitle.model.CantGetSubtitleException
import gwenedeg.shared.generated.resources.Res
class LrcSubtitleRepository(
internal class LrcSubtitleRepository(
private val parser: LrcParser,
private val logger: Logger,
) : SubtitleRepository {
override suspend fun getSubtitle(resourcePath: String): Subtitle =
override suspend fun getSubtitle(content: String): Subtitle =
try {
parser.parse(Res.readBytes(resourcePath).decodeToString())
parser.parse(content = content)
} catch (exception: Exception) {
val message = "Failed to load subtitle: $resourcePath"
val message = "Failed to load subtitle: $content"
logger.error(message = message, throwable = exception)
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,5 +1,5 @@
package fr.ajaury.gwenedeg.subtitle.domain
interface SubtitleRepository {
suspend fun getSubtitle(resourcePath: String): Subtitle
suspend fun getSubtitle(content: String): Subtitle
}
+3
View File
@@ -40,3 +40,6 @@ include(":shared")
// Core modules
include(":core:audioplayer")
include(":core:logging")
// Data modules
include(":data:subtitle")
+3
View File
@@ -62,6 +62,9 @@ kotlin {
implementation(projects.core.audioplayer)
implementation(projects.core.logging)
// Data modules
implementation(projects.data.subtitle)
implementation(libs.compose.runtime)
implementation(libs.compose.foundation)
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.ui.PlayerScreen
import fr.ajaury.gwenedeg.records.ui.RecordsScreen
import fr.ajaury.gwenedeg.subtitle.di.subtitleModule
import fr.ajaury.gwenedeg.theme.GwenedegTheme
import org.koin.compose.KoinApplication
import org.koin.dsl.koinConfiguration
@@ -25,7 +26,8 @@ fun App() {
modules(
sharedModule,
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.domain.RecordRepository
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
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 fr.ajaury.gwenedeg.resourcereader.data.ComposeResourceReader
import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader
import org.koin.core.module.dsl.bind
import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.viewModelOf
@@ -15,9 +13,7 @@ import org.koin.dsl.module
val sharedModule = module {
factoryOf(::InMemoryRecordRepository) { bind<RecordRepository>() }
factoryOf(::LrcParser)
factoryOf(::LrcSubtitleRepository) { bind<SubtitleRepository>() }
factoryOf(::GetCurrentSubtitleIndexUseCase)
viewModelOf(::RecordsViewModel)
viewModelOf(::PlayerViewModel)
factoryOf(::ComposeResourceReader) { bind<ResourceReader>() }
}
@@ -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.AudioSessionManager
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.Subtitle
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
@@ -26,6 +27,7 @@ import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
class PlayerViewModel(
private val resourceReader: ResourceReader,
private val audioPlayer: AudioPlayer,
private val audioSessionManager: AudioSessionManager,
private val subtitleRepository: SubtitleRepository,
@@ -92,7 +94,8 @@ class PlayerViewModel(
viewModelScope.launch {
subtitle.value =
try {
subtitleRepository.getSubtitle(resourcePath)
val content = resourceReader.read(resourcePath = resourcePath)
subtitleRepository.getSubtitle(content = content)
} catch (exception: Exception) {
logger.error(
message = "Failed to load subtitle: $resourcePath",
@@ -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()
}
@@ -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
}