From 0c4dbff92929ff0cf815580dbfbe0334ad38057a Mon Sep 17 00:00:00 2001 From: Antoine Jaury Date: Thu, 2 Jul 2026 16:55:14 +0200 Subject: [PATCH] refactor: migrate resource handling to new `data.resources` module --- data/resources/build.gradle.kts | 66 +++++++++++++++++++ .../drawable/compose-multiplatform.xml | 0 .../resources}/data/ComposeResourceReader.kt | 8 ++- .../gwenedeg/resources/di/ResourcesModule.kt | 12 ++++ .../resources/domain/ResourceReader.kt | 13 ++++ settings.gradle.kts | 1 + shared/build.gradle.kts | 2 +- .../kotlin/fr/ajaury/gwenedeg/di/Modules.kt | 5 +- .../player/data/PlaybackRepositoryImpl.kt | 5 +- .../resourcereader/domain/ResourceReader.kt | 9 --- 10 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 data/resources/build.gradle.kts rename {shared => data/resources}/src/commonMain/composeResources/drawable/compose-multiplatform.xml (100%) rename {shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader => data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources}/data/ComposeResourceReader.kt (62%) create mode 100644 data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/di/ResourcesModule.kt create mode 100644 data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/domain/ResourceReader.kt delete mode 100644 shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/domain/ResourceReader.kt diff --git a/data/resources/build.gradle.kts b/data/resources/build.gradle.kts new file mode 100644 index 0000000..35a3f54 --- /dev/null +++ b/data/resources/build.gradle.kts @@ -0,0 +1,66 @@ +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.androidMultiplatformLibrary) + alias(libs.plugins.composeMultiplatform) + alias(libs.plugins.composeCompiler) +} + +kotlin { + iosArm64() + iosSimulatorArm64() + + jvm() + + js { + browser() + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + } + + androidLibrary { + namespace = "fr.ajaury.gwenedeg.resources" + compileSdk = + libs.versions.androidCompileSdk + .get() + .toInt() + minSdk = + libs.versions.androidMinSdk + .get() + .toInt() + + compilerOptions { + jvmTarget = JvmTarget.JVM_11 + } + androidResources { + enable = true + } + } + + sourceSets { + commonMain.dependencies { + // Core modules + implementation(projects.core.coroutines) + + // Compose resources + implementation(libs.compose.runtime) + implementation(libs.compose.components.resources) + + // Coroutines + implementation(libs.kotlinx.coroutinesCore) + + // DI + implementation(project.dependencies.platform(libs.koin.bom)) + implementation(libs.koin.core) + } + } +} + +compose.resources { + packageOfResClass = "fr.ajaury.gwenedeg.resources.generated.resources" +} diff --git a/shared/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/data/resources/src/commonMain/composeResources/drawable/compose-multiplatform.xml similarity index 100% rename from shared/src/commonMain/composeResources/drawable/compose-multiplatform.xml rename to data/resources/src/commonMain/composeResources/drawable/compose-multiplatform.xml diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/data/ComposeResourceReader.kt b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/data/ComposeResourceReader.kt similarity index 62% rename from shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/data/ComposeResourceReader.kt rename to data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/data/ComposeResourceReader.kt index f934df9..1b30cb2 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/data/ComposeResourceReader.kt +++ b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/data/ComposeResourceReader.kt @@ -1,8 +1,8 @@ -package fr.ajaury.gwenedeg.resourcereader.data +package fr.ajaury.gwenedeg.resources.data import fr.ajaury.gwenedeg.core.coroutines.domain.DispatcherProvider -import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader -import gwenedeg.shared.generated.resources.Res +import fr.ajaury.gwenedeg.resources.domain.ResourceReader +import fr.ajaury.gwenedeg.resources.generated.resources.Res import kotlinx.coroutines.withContext internal class ComposeResourceReader( @@ -12,4 +12,6 @@ internal class ComposeResourceReader( withContext(dispatcherProvider.io) { Res.readBytes(resourcePath).decodeToString() } + + override fun uri(resourcePath: String): String = Res.getUri(resourcePath) } diff --git a/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/di/ResourcesModule.kt b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/di/ResourcesModule.kt new file mode 100644 index 0000000..b326a85 --- /dev/null +++ b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/di/ResourcesModule.kt @@ -0,0 +1,12 @@ +package fr.ajaury.gwenedeg.resources.di + +import fr.ajaury.gwenedeg.resources.data.ComposeResourceReader +import fr.ajaury.gwenedeg.resources.domain.ResourceReader +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 resourcesModule: Module = module { + factoryOf(::ComposeResourceReader) { bind() } +} diff --git a/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/domain/ResourceReader.kt b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/domain/ResourceReader.kt new file mode 100644 index 0000000..bc038cd --- /dev/null +++ b/data/resources/src/commonMain/kotlin/fr/ajaury/gwenedeg/resources/domain/ResourceReader.kt @@ -0,0 +1,13 @@ +package fr.ajaury.gwenedeg.resources.domain + +/** + * Provides access to the app's bundled resources. Implemented by this module, which owns the + * resource bundle, so consumer modules stay free of any platform/resource dependency. + */ +interface ResourceReader { + /** Reads the raw text content of the resource at [resourcePath]. */ + suspend fun read(resourcePath: String): String + + /** Returns a platform URI pointing to the resource at [resourcePath]. */ + fun uri(resourcePath: String): String +} diff --git a/settings.gradle.kts b/settings.gradle.kts index cb2c3a8..86f394a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,4 +44,5 @@ include(":core:logging") include(":core:model") // Data modules +include(":data:resources") include(":data:subtitle") diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index 41845de..6f65852 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -65,6 +65,7 @@ kotlin { implementation(projects.core.model) // Data modules + implementation(projects.data.resources) implementation(projects.data.subtitle) implementation(libs.compose.runtime) @@ -72,7 +73,6 @@ kotlin { implementation(libs.compose.material3) implementation(libs.compose.material.icons.extended) implementation(libs.compose.ui) - implementation(libs.compose.components.resources) implementation(libs.compose.uiToolingPreview) // Lifecycle diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt index 77436ef..f92bcd9 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/di/Modules.kt @@ -9,8 +9,7 @@ import fr.ajaury.gwenedeg.preferences.domain.PreferencesRepository 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.resourcereader.data.ComposeResourceReader -import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader +import fr.ajaury.gwenedeg.resources.di.resourcesModule import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.factoryOf import org.koin.core.module.dsl.singleOf @@ -19,10 +18,10 @@ import org.koin.dsl.module val sharedModule = module { includes(loggingModule) + includes(resourcesModule) factoryOf(::InMemoryRecordRepository) { bind() } singleOf(::PlaybackRepositoryImpl) { bind() } singleOf(::PreferencesRepositoryImpl) { bind() } viewModelOf(::RecordsViewModel) viewModelOf(::PlayerViewModel) - factoryOf(::ComposeResourceReader) { bind() } } diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt index 6bf1a79..ac9b740 100644 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt +++ b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/player/data/PlaybackRepositoryImpl.kt @@ -8,11 +8,10 @@ import fr.ajaury.gwenedeg.player.domain.PlaybackRepository import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlayerState -import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader +import fr.ajaury.gwenedeg.resources.domain.ResourceReader import fr.ajaury.gwenedeg.subtitle.domain.GetCurrentSubtitleIndexUseCase import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository import fr.ajaury.gwenedeg.subtitle.model.Subtitle -import gwenedeg.shared.generated.resources.Res import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow @@ -96,7 +95,7 @@ internal class PlaybackRepositoryImpl( private fun playAudio(filePath: String) { try { - audioPlayer.load(uri = Res.getUri(filePath)) + audioPlayer.load(uri = resourceReader.uri(filePath)) audioPlayer.play() } catch (exception: Exception) { logger.error(message = "Failed to play audio: $filePath", throwable = exception) diff --git a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/domain/ResourceReader.kt b/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/domain/ResourceReader.kt deleted file mode 100644 index 63d3fe6..0000000 --- a/shared/src/commonMain/kotlin/fr/ajaury/gwenedeg/resourcereader/domain/ResourceReader.kt +++ /dev/null @@ -1,9 +0,0 @@ -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 -}