refactor: centralize domain models into core.model module

This commit is contained in:
2026-07-01 17:06:02 +02:00
parent ff1844e49f
commit 657d39a293
28 changed files with 190 additions and 76 deletions
+45
View File
@@ -0,0 +1,45 @@
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.core.model"
compileSdk =
libs.versions.androidCompileSdk
.get()
.toInt()
minSdk =
libs.versions.androidMinSdk
.get()
.toInt()
compilerOptions {
jvmTarget = JvmTarget.JVM_11
}
}
sourceSets {
commonTest.dependencies {
implementation(libs.kotlin.test)
}
}
}
@@ -0,0 +1,10 @@
package fr.ajaury.gwenedeg.core.model
/**
* [translation] is the optional translation of [transcription] in the user's language
* (e.g. French), shown alongside the original.
*/
data class Phrase(
val transcription: String,
val translation: String? = null,
)
@@ -1,4 +1,4 @@
package fr.ajaury.gwenedeg.records.domain package fr.ajaury.gwenedeg.core.model
data class Record( data class Record(
val id: Int, val id: Int,
+1
View File
@@ -42,6 +42,7 @@ kotlin {
// Core modules // Core modules
implementation(projects.core.coroutines) implementation(projects.core.coroutines)
implementation(projects.core.logging) implementation(projects.core.logging)
implementation(projects.core.model)
// Coroutines // Coroutines
implementation(libs.kotlinx.coroutinesCore) implementation(libs.kotlinx.coroutinesCore)
@@ -1,7 +1,5 @@
package fr.ajaury.gwenedeg.subtitle.data package fr.ajaury.gwenedeg.subtitle.data
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
/** /**
@@ -14,23 +12,23 @@ 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}])+""")
fun parse(content: String): Subtitle { fun parse(content: String): List<TimeCodedText> =
val lines = content content
.lineSequence() .lineSequence()
.flatMap { line -> parseLine(line) } .flatMap { line -> parseLine(line) }
.sortedBy { it.startTime } .sortedBy { it.startTime }
.toList() .toList()
return Subtitle(lines)
}
private fun parseLine(line: String): List<SubtitleLine> { private fun parseLine(line: String): List<TimeCodedText> {
val tags = leadingTimeTags.find(line) ?: return emptyList() val tags = leadingTimeTags.find(line) ?: return emptyList()
val text = line.substring(tags.range.last + 1).trim() val text = line.substring(tags.range.last + 1).trim()
return timeTag return timeTag
.findAll(tags.value) .findAll(tags.value)
.mapNotNull { match -> .mapNotNull { match ->
SubtitleLine(startTime = match.toMs().milliseconds, text = text) TimeCodedText(
.takeIf { it.text.isNotBlank() } startTime = match.toMs().milliseconds,
text = text,
).takeIf { text.isNotBlank() }
}.toList() }.toList()
} }
@@ -2,9 +2,11 @@ package fr.ajaury.gwenedeg.subtitle.data
import fr.ajaury.gwenedeg.core.coroutines.domain.DispatcherProvider import fr.ajaury.gwenedeg.core.coroutines.domain.DispatcherProvider
import fr.ajaury.gwenedeg.core.logging.domain.Logger import fr.ajaury.gwenedeg.core.logging.domain.Logger
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle import fr.ajaury.gwenedeg.core.model.Phrase
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 fr.ajaury.gwenedeg.subtitle.model.Subtitle
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
internal class LrcSubtitleRepository( internal class LrcSubtitleRepository(
@@ -13,28 +15,33 @@ internal class LrcSubtitleRepository(
private val logger: Logger, private val logger: Logger,
) : SubtitleRepository { ) : SubtitleRepository {
override suspend fun getSubtitle( override suspend fun getSubtitle(
content: String, transcriptionContent: String,
translationContent: String?, translationContent: String?,
): Subtitle = ): Subtitle =
withContext(dispatcherProvider.default) { withContext(dispatcherProvider.default) {
try { try {
val subtitle = parser.parse(content = content) val transcription = parser.parse(content = transcriptionContent)
val translation = translationContent?.let { parser.parse(content = it) } val translation = translationContent?.let { parser.parse(content = it) }
subtitle.mergeTranslation(translation) transcription.mergeTranslation(translation)
} catch (exception: Exception) { } catch (exception: Exception) {
val message = "Failed to load subtitle: $content" val message = "Failed to load subtitle: $transcriptionContent"
logger.error(message = message, throwable = exception) logger.error(message = message, throwable = exception)
throw CantGetSubtitleException(message) throw CantGetSubtitleException(message)
} }
} }
/** Attaches each [translation] line's text to the matching original line, paired by timestamp. */ /** Attaches each [translation] line's text to the matching original line, paired by timestamp. */
private fun Subtitle.mergeTranslation(translation: Subtitle?): Subtitle { private fun List<TimeCodedText>.mergeTranslation(translation: List<TimeCodedText>?): Subtitle {
if (translation == null) return this val translationByStart = translation?.associate { it.startTime to it.text } ?: emptyMap()
val translationByStart = translation.lines.associate { it.startTime to it.text }
return Subtitle( return Subtitle(
lines = lines.map { line -> lines = this.map {
line.copy(translation = translationByStart[line.startTime]) SubtitleLine(
startTime = it.startTime,
phrase = Phrase(
transcription = it.text,
translation = translationByStart[it.startTime],
),
)
}, },
) )
} }
@@ -0,0 +1,11 @@
package fr.ajaury.gwenedeg.subtitle.data
import kotlin.time.Duration
/**
* A single timed text cue: [text] becomes active at [startTime].
*/
data class TimeCodedText(
val startTime: Duration,
val text: String,
)
@@ -1,5 +1,6 @@
package fr.ajaury.gwenedeg.subtitle.domain package fr.ajaury.gwenedeg.subtitle.domain
import fr.ajaury.gwenedeg.subtitle.model.Subtitle
import kotlin.time.Duration import kotlin.time.Duration
class GetCurrentSubtitleIndexUseCase { class GetCurrentSubtitleIndexUseCase {
@@ -1,13 +0,0 @@
package fr.ajaury.gwenedeg.subtitle.domain
import kotlin.time.Duration
/**
* A single timed subtitle cue: [text] becomes active at [startTime]. [translation] is the optional
* translation of [text] in the user's language (e.g. French), shown alongside the original.
*/
data class SubtitleLine(
val startTime: Duration,
val text: String,
val translation: String? = null,
)
@@ -1,12 +1,14 @@
package fr.ajaury.gwenedeg.subtitle.domain package fr.ajaury.gwenedeg.subtitle.domain
import fr.ajaury.gwenedeg.subtitle.model.Subtitle
interface SubtitleRepository { interface SubtitleRepository {
/** /**
* Parses the subtitle [content]. When [translationContent] is provided, each line is matched to * Parses the subtitle [transcriptionContent]. When [translationContent] is provided, each line is matched to
* its translation by timestamp. * its translation by timestamp.
*/ */
suspend fun getSubtitle( suspend fun getSubtitle(
content: String, transcriptionContent: String,
translationContent: String? = null, translationContent: String? = null,
): Subtitle ): Subtitle
} }
@@ -1,4 +1,4 @@
package fr.ajaury.gwenedeg.subtitle.domain package fr.ajaury.gwenedeg.subtitle.model
data class Subtitle( data class Subtitle(
val lines: List<SubtitleLine>, val lines: List<SubtitleLine>,
@@ -0,0 +1,12 @@
package fr.ajaury.gwenedeg.subtitle.model
import fr.ajaury.gwenedeg.core.model.Phrase
import kotlin.time.Duration
/**
* A single timed subtitle cue: [phrase] becomes active at [startTime].
*/
data class SubtitleLine(
val startTime: Duration,
val phrase: Phrase,
)
@@ -1,8 +1,9 @@
package fr.ajaury.gwenedeg.subtitle package fr.ajaury.gwenedeg.subtitle
import fr.ajaury.gwenedeg.core.model.Phrase
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.model.Subtitle
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@@ -14,9 +15,9 @@ class GetCurrentSubtitleIndexUseCaseTest {
Subtitle( Subtitle(
lines = lines =
listOf( listOf(
SubtitleLine(startTime = 0.milliseconds, text = "Demat deoc'h !"), SubtitleLine(startTime = 0.milliseconds, phrase = Phrase(transcription = "Demat deoc'h !")),
SubtitleLine(startTime = 4310.milliseconds, text = "Kenavo !"), SubtitleLine(startTime = 4310.milliseconds, phrase = Phrase(transcription = "Kenavo !")),
SubtitleLine(startTime = 7350.milliseconds, text = "Kenavo emberr !"), SubtitleLine(startTime = 7350.milliseconds, phrase = Phrase(transcription = "Kenavo emberr !")),
), ),
) )
@@ -23,31 +23,31 @@ class LrcParserTest {
@Test @Test
fun ignores_metadata_tags_and_keeps_only_timed_cues() { fun ignores_metadata_tags_and_keeps_only_timed_cues() {
val subtitle = parser.parse(sample) val lines = parser.parse(sample)
assertEquals(3, subtitle.lines.size) assertEquals(3, lines.size)
} }
@Test @Test
fun converts_timestamp_to_milliseconds() { fun converts_timestamp_to_milliseconds() {
val subtitle = parser.parse(sample) val lines = parser.parse(sample)
assertEquals(0.milliseconds, subtitle.lines[0].startTime) assertEquals(0.milliseconds, lines[0].startTime)
assertEquals(4310.milliseconds, subtitle.lines[1].startTime) assertEquals(4310.milliseconds, lines[1].startTime)
assertEquals(7350.milliseconds, subtitle.lines[2].startTime) assertEquals(7350.milliseconds, lines[2].startTime)
} }
@Test @Test
fun parses_three_digit_fraction_as_milliseconds() { fun parses_three_digit_fraction_as_milliseconds() {
val subtitle = parser.parse("[00:01.250]Hello") val lines = parser.parse("[00:01.250]Hello")
assertEquals(1250.milliseconds, subtitle.lines.single().startTime) assertEquals(1250.milliseconds, lines.single().startTime)
} }
@Test @Test
fun sorts_cues_ascending_by_start() { fun sorts_cues_ascending_by_start() {
val subtitle = parser.parse("[00:05.00]Later\n[00:01.00]Earlier") val lines = parser.parse("[00:05.00]Later\n[00:01.00]Earlier")
assertEquals(listOf("Earlier", "Later"), subtitle.lines.map { it.text }) assertEquals(listOf("Earlier", "Later"), lines.map { it.text })
} }
} }
+1
View File
@@ -41,6 +41,7 @@ include(":shared")
include(":core:audioplayer") include(":core:audioplayer")
include(":core:coroutines") include(":core:coroutines")
include(":core:logging") include(":core:logging")
include(":core:model")
// Data modules // Data modules
include(":data:subtitle") include(":data:subtitle")
+1
View File
@@ -62,6 +62,7 @@ kotlin {
implementation(projects.core.audioplayer) implementation(projects.core.audioplayer)
implementation(projects.core.coroutines) implementation(projects.core.coroutines)
implementation(projects.core.logging) implementation(projects.core.logging)
implementation(projects.core.model)
// Data modules // Data modules
implementation(projects.data.subtitle) implementation(projects.data.subtitle)
@@ -1,15 +1,15 @@
package fr.ajaury.gwenedeg.player.data package fr.ajaury.gwenedeg.player.data
import fr.ajaury.gwenedeg.core.logging.domain.Logger import fr.ajaury.gwenedeg.core.logging.domain.Logger
import fr.ajaury.gwenedeg.core.model.Record
import fr.ajaury.gwenedeg.player.domain.AudioPlayer import fr.ajaury.gwenedeg.player.domain.AudioPlayer
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackState
import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlaybackTiming
import fr.ajaury.gwenedeg.records.domain.Record
import fr.ajaury.gwenedeg.resourcereader.data.domain.ResourceReader 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.SubtitleRepository import fr.ajaury.gwenedeg.subtitle.domain.SubtitleRepository
import fr.ajaury.gwenedeg.subtitle.model.Subtitle
import gwenedeg.shared.generated.resources.Res import gwenedeg.shared.generated.resources.Res
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -91,7 +91,7 @@ internal class PlaybackRepositoryImpl(
val translationContent = record.translationSubtitleResourcePath val translationContent = record.translationSubtitleResourcePath
?.let { resourceReader.read(resourcePath = it) } ?.let { resourceReader.read(resourcePath = it) }
subtitleRepository.getSubtitle( subtitleRepository.getSubtitle(
content = content, transcriptionContent = content,
translationContent = translationContent, translationContent = translationContent,
) )
} catch (exception: Exception) { } catch (exception: Exception) {
@@ -1,9 +1,9 @@
package fr.ajaury.gwenedeg.player.domain package fr.ajaury.gwenedeg.player.domain
import fr.ajaury.gwenedeg.core.model.Record
import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackState
import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlaybackTiming
import fr.ajaury.gwenedeg.records.domain.Record import fr.ajaury.gwenedeg.subtitle.model.Subtitle
import fr.ajaury.gwenedeg.subtitle.domain.Subtitle
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlin.time.Duration import kotlin.time.Duration
@@ -22,11 +22,12 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.ajaury.gwenedeg.core.model.Phrase
import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackState
import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlaybackTiming
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerUiState import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerUiState
import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel import fr.ajaury.gwenedeg.player.ui.viewmodel.PlayerViewModel
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
import fr.ajaury.gwenedeg.theme.GwenedegTheme import fr.ajaury.gwenedeg.theme.GwenedegTheme
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.parameter.parametersOf import org.koin.core.parameter.parametersOf
@@ -133,9 +134,27 @@ fun PlayerScreen(
private val previewSubtitleLines = private val previewSubtitleLines =
listOf( listOf(
SubtitleLine(startTime = 0.milliseconds, text = "Demat deoc'h !", translation = "Bonjour !"), SubtitleLine(
SubtitleLine(startTime = 4310.milliseconds, text = "Kenavo !", translation = "Au revoir !"), startTime = 0.milliseconds,
SubtitleLine(startTime = 7350.milliseconds, text = "Kenavo emberr !", translation = "À ce soir !"), phrase = Phrase(
transcription = "Demat deoc'h !",
translation = "Bonjour !",
),
),
SubtitleLine(
startTime = 4310.milliseconds,
phrase = Phrase(
transcription = "Kenavo !",
translation = "Au revoir !",
),
),
SubtitleLine(
startTime = 7350.milliseconds,
phrase = Phrase(
transcription = "Kenavo emberr !",
translation = "À ce soir !",
),
),
) )
@Preview @Preview
@@ -27,7 +27,8 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine import fr.ajaury.gwenedeg.core.model.Phrase
import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
import fr.ajaury.gwenedeg.theme.GwenedegTheme import fr.ajaury.gwenedeg.theme.GwenedegTheme
import kotlin.math.abs import kotlin.math.abs
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
@@ -132,12 +133,12 @@ private fun Subtitle(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(2.dp), verticalArrangement = Arrangement.spacedBy(2.dp),
) { ) {
MainTextLine( TranscriptionLine(
text = line.text, text = line.phrase.transcription,
isCurrent = isCurrent, isCurrent = isCurrent,
contentColor = contentColor, contentColor = contentColor,
) )
line.translation?.let { translation -> line.phrase.translation?.let { translation ->
TranslationLine( TranslationLine(
text = translation, text = translation,
isCurrent = isCurrent, isCurrent = isCurrent,
@@ -148,7 +149,7 @@ private fun Subtitle(
} }
@Composable @Composable
private fun MainTextLine( private fun TranscriptionLine(
text: String, text: String,
isCurrent: Boolean, isCurrent: Boolean,
contentColor: Color, contentColor: Color,
@@ -191,9 +192,27 @@ private fun TranslationLine(
private val previewSubtitleLines = private val previewSubtitleLines =
listOf( listOf(
SubtitleLine(startTime = 0.milliseconds, text = "Demat deoc'h !", translation = "Bonjour !"), SubtitleLine(
SubtitleLine(startTime = 4310.milliseconds, text = "Kenavo !", translation = "Au revoir !"), startTime = 0.milliseconds,
SubtitleLine(startTime = 7350.milliseconds, text = "Kenavo emberr !", translation = "À ce soir !"), phrase = Phrase(
transcription = "Demat deoc'h !",
translation = "Bonjour !",
),
),
SubtitleLine(
startTime = 4310.milliseconds,
phrase = Phrase(
transcription = "Kenavo !",
translation = "Au revoir !",
),
),
SubtitleLine(
startTime = 7350.milliseconds,
phrase = Phrase(
transcription = "Kenavo emberr !",
translation = "À ce soir !",
),
),
) )
@Preview @Preview
@@ -2,7 +2,7 @@ package fr.ajaury.gwenedeg.player.ui.viewmodel
import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackState
import fr.ajaury.gwenedeg.player.model.PlaybackTiming import fr.ajaury.gwenedeg.player.model.PlaybackTiming
import fr.ajaury.gwenedeg.subtitle.domain.SubtitleLine import fr.ajaury.gwenedeg.subtitle.model.SubtitleLine
data class PlayerUiState( data class PlayerUiState(
val recordTitle: String = "", val recordTitle: String = "",
@@ -3,10 +3,10 @@ package fr.ajaury.gwenedeg.player.ui.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import fr.ajaury.gwenedeg.core.logging.domain.Logger import fr.ajaury.gwenedeg.core.logging.domain.Logger
import fr.ajaury.gwenedeg.core.model.Record
import fr.ajaury.gwenedeg.player.domain.AudioSessionManager import fr.ajaury.gwenedeg.player.domain.AudioSessionManager
import fr.ajaury.gwenedeg.player.domain.PlaybackRepository import fr.ajaury.gwenedeg.player.domain.PlaybackRepository
import fr.ajaury.gwenedeg.player.model.PlaybackState import fr.ajaury.gwenedeg.player.model.PlaybackState
import fr.ajaury.gwenedeg.records.domain.Record
import fr.ajaury.gwenedeg.records.domain.RecordRepository import fr.ajaury.gwenedeg.records.domain.RecordRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
@@ -1,7 +1,7 @@
package fr.ajaury.gwenedeg.records.data package fr.ajaury.gwenedeg.records.data
import fr.ajaury.gwenedeg.records.domain.Record
import fr.ajaury.gwenedeg.records.domain.RecordRepository import fr.ajaury.gwenedeg.records.domain.RecordRepository
import fr.ajaury.gwenedeg.core.model.Record
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
@@ -1,5 +1,6 @@
package fr.ajaury.gwenedeg.records.domain package fr.ajaury.gwenedeg.records.domain
import fr.ajaury.gwenedeg.core.model.Record
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface RecordRepository { interface RecordRepository {
@@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import fr.ajaury.gwenedeg.records.domain.Record import fr.ajaury.gwenedeg.core.model.Record
@Composable @Composable
fun RecordView( fun RecordView(
@@ -1,9 +1,7 @@
package fr.ajaury.gwenedeg.records.ui package fr.ajaury.gwenedeg.records.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeContentPadding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -14,7 +12,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import fr.ajaury.gwenedeg.records.domain.Record import fr.ajaury.gwenedeg.core.model.Record
import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel import fr.ajaury.gwenedeg.records.ui.viewmodel.RecordsViewModel
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@@ -2,8 +2,8 @@ package fr.ajaury.gwenedeg.records.ui.viewmodel
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import fr.ajaury.gwenedeg.records.domain.Record
import fr.ajaury.gwenedeg.records.domain.RecordRepository import fr.ajaury.gwenedeg.records.domain.RecordRepository
import fr.ajaury.gwenedeg.core.model.Record
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn