From e2fe415a1af8ac55e64c2512403bd047864479a8 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sun, 26 Jul 2026 02:05:55 +0200 Subject: [PATCH] Add MantraTranslationArtifactVersion detail screen Translation cards in the artifact detail's Translations list are now clickable, opening a translation detail screen. - New TranslationDetailRoute + TranslationDetailScreen/ViewModel/UIState. The screen shows the translation's details (name, dialect, visibility, license, source version, author, created) and its chapters ordered by index, each with translation progress (translated/total chunks, where a chunk counts as translated once its text is non-empty). - DAO queries getTranslationById, translation chapters by translationArtifactVersionId, and translation chunks by translationChapterId (validated by Room), exposed via MantraRepository (impl + NO_OP). - Extract the DetailRow composable (was private to ArtifactDetailScreen) into a shared widgets/DetailRow.kt used by both detail screens. Co-Authored-By: Claude Opus 4.8 --- .../MantraTranslationArtifactVersionDao.kt | 3 + .../dao/MantraTranslationChapterDao.kt | 4 + .../database/dao/MantraTranslationChunkDao.kt | 4 + .../repository/DatabaseMantraRepository.kt | 9 + .../compose/repository/MantraRepository.kt | 14 ++ .../ui/composable/ArtifactDetailScreen.kt | 23 +- .../ui/composable/TranslationDetailScreen.kt | 205 ++++++++++++++++++ .../ui/composable/navigation/MantraNavHost.kt | 16 ++ .../routes/TranslationDetailRoute.kt | 11 + .../ui/composable/widgets/DetailRow.kt | 14 ++ .../view/model/TranslationDetailViewModel.kt | 82 +++++++ .../ui/view/state/TranslationDetailUIState.kt | 24 ++ 12 files changed, 400 insertions(+), 9 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationDetailScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/TranslationDetailRoute.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/DetailRow.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/TranslationDetailViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/TranslationDetailUIState.kt diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationArtifactVersionDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationArtifactVersionDao.kt index 8ef09237..528ab188 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationArtifactVersionDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationArtifactVersionDao.kt @@ -23,4 +23,7 @@ interface MantraTranslationArtifactVersionDao { """ ) suspend fun getTranslationsByArtifactId(artifactId: String): List + + @Query("SELECT * FROM MantraTranslationArtifactVersion WHERE id = :id") + suspend fun getTranslationById(id: String): MantraTranslationArtifactVersion? } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChapterDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChapterDao.kt index 7cf2f13e..0cdcf176 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChapterDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChapterDao.kt @@ -3,6 +3,7 @@ package press.mantra.compose.database.dao import androidx.room3.Dao import androidx.room3.Insert import androidx.room3.OnConflictStrategy.Companion.IGNORE +import androidx.room3.Query import androidx.room3.Upsert import press.mantra.compose.database.model.MantraTranslationChapter @@ -10,4 +11,7 @@ import press.mantra.compose.database.model.MantraTranslationChapter interface MantraTranslationChapterDao { @Upsert suspend fun upsert(mantraTranslationChapter: MantraTranslationChapter) + + @Query("SELECT * FROM MantraTranslationChapter WHERE translationArtifactVersionId = :translationArtifactVersionId ORDER BY `index` ASC") + suspend fun getTranslationChaptersByTranslationArtifactVersionId(translationArtifactVersionId: String): List } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChunkDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChunkDao.kt index 248c8691..0afc076a 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChunkDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraTranslationChunkDao.kt @@ -3,6 +3,7 @@ package press.mantra.compose.database.dao import androidx.room3.Dao import androidx.room3.Insert import androidx.room3.OnConflictStrategy.Companion.IGNORE +import androidx.room3.Query import androidx.room3.Upsert import press.mantra.compose.database.model.MantraTranslationChunk @@ -10,4 +11,7 @@ import press.mantra.compose.database.model.MantraTranslationChunk interface MantraTranslationChunkDao { @Upsert suspend fun upsert(mantraTranslationChunk: MantraTranslationChunk) + + @Query("SELECT * FROM MantraTranslationChunk WHERE translationChapterId = :translationChapterId ORDER BY `index` ASC") + suspend fun getTranslationChunksByTranslationChapterId(translationChapterId: String): List } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/repository/DatabaseMantraRepository.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/repository/DatabaseMantraRepository.kt index 30e085a7..06b2984c 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/repository/DatabaseMantraRepository.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/repository/DatabaseMantraRepository.kt @@ -158,6 +158,15 @@ class DatabaseMantraRepository( override suspend fun getTranslationsForArtifact(artifactId: String): List = database.mantraTranslationArtifactVersionDao().getTranslationsByArtifactId(artifactId) + override suspend fun getTranslation(id: String): MantraTranslationArtifactVersion? = + database.mantraTranslationArtifactVersionDao().getTranslationById(id) + + override suspend fun getTranslationChapters(translationArtifactVersionId: String): List = + database.mantraTranslationChapterDao().getTranslationChaptersByTranslationArtifactVersionId(translationArtifactVersionId) + + override suspend fun getTranslationChunks(translationChapterId: String): List = + database.mantraTranslationChunkDao().getTranslationChunksByTranslationChapterId(translationChapterId) + override suspend fun addChapter( artifactId: String, name: String, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt index 74e5c27f..7e4fa5fb 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt @@ -7,6 +7,8 @@ import press.mantra.compose.database.model.MantraChapter import press.mantra.compose.database.model.MantraChunk import press.mantra.compose.database.model.MantraDialect import press.mantra.compose.database.model.MantraTranslationArtifactVersion +import press.mantra.compose.database.model.MantraTranslationChapter +import press.mantra.compose.database.model.MantraTranslationChunk import press.mantra.compose.database.model.MarmotInnerEvent interface MantraRepository { @@ -24,6 +26,12 @@ interface MantraRepository { suspend fun getTranslationsForArtifact(artifactId: String): List + suspend fun getTranslation(id: String): MantraTranslationArtifactVersion? + + suspend fun getTranslationChapters(translationArtifactVersionId: String): List + + suspend fun getTranslationChunks(translationChapterId: String): List + /** * Start a translation of the artifact into the given dialect: creates a * MantraTranslationArtifactVersion for the artifact's latest version, and @@ -99,6 +107,12 @@ interface MantraRepository { override suspend fun getTranslationsForArtifact(artifactId: String): List = emptyList() + override suspend fun getTranslation(id: String): MantraTranslationArtifactVersion? = null + + override suspend fun getTranslationChapters(translationArtifactVersionId: String): List = emptyList() + + override suspend fun getTranslationChunks(translationChapterId: String): List = emptyList() + override suspend fun addTranslation( artifactId: String, dialectId: String, diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt index 2c053e51..d94425f6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt @@ -38,10 +38,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.quartz.nip01Core.core.HexKey import press.mantra.compose.database.model.MantraArtifact import press.mantra.compose.repository.MantraRepository +import press.mantra.compose.ui.composable.widgets.DetailRow import press.mantra.compose.ui.composable.navigation.routes.AddChapterRoute import press.mantra.compose.ui.composable.navigation.routes.AddTranslationRoute import press.mantra.compose.ui.composable.navigation.routes.ChapterDetailRoute import press.mantra.compose.ui.composable.navigation.routes.Route +import press.mantra.compose.ui.composable.navigation.routes.TranslationDetailRoute import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator import press.mantra.compose.ui.theme.TorchTheme import press.mantra.compose.ui.view.model.ArtifactDetailViewModel @@ -228,7 +230,18 @@ fun ArtifactDetailScreen( items = artifactDetailUIState.translations, key = { translation -> translation.id } ) { translation -> - Card { + Card( + onClick = { + onNavigateToRoute.invoke( + TranslationDetailRoute( + activeUserPublicKey = activeUserPublicKey, + translationArtifactVersionId = translation.id, + chatRoomId = chatRoomId, + relayHint = relayHint + ) + ) + } + ) { ListItem( leadingContent = { Icon( @@ -294,14 +307,6 @@ fun ArtifactDetailScreen( } } -@Composable -private fun DetailRow(label: String, value: String) { - ListItem( - overlineContent = { Text(label) }, - headlineContent = { Text(value) } - ) -} - @Preview @Composable private fun ArtifactDetailScreenPreview() { diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationDetailScreen.kt new file mode 100644 index 00000000..48000503 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/TranslationDetailScreen.kt @@ -0,0 +1,205 @@ +package press.mantra.compose.ui.composable + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Article +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ListItem +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import press.mantra.compose.database.model.MantraTranslationArtifactVersion +import press.mantra.compose.repository.MantraRepository +import press.mantra.compose.ui.composable.widgets.DetailRow +import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator +import press.mantra.compose.ui.theme.TorchTheme +import press.mantra.compose.ui.view.model.TranslationDetailViewModel +import press.mantra.compose.ui.view.state.TranslationDetailUIState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun TranslationDetailScreen( + activeUserPublicKey: HexKey, + translationArtifactVersionId: String, + chatRoomId: String, + relayHint: String?, + initialTranslationDetailUIState: TranslationDetailUIState = TranslationDetailUIState.Loading, + mantraRepository: MantraRepository, + onNavigateBack: () -> Unit, +) { + val translationDetailViewModel: TranslationDetailViewModel = viewModel( + factory = TranslationDetailViewModel.factory( + activeUserPublicKey = activeUserPublicKey, + translationArtifactVersionId = translationArtifactVersionId, + chatRoomId = chatRoomId, + relayHint = relayHint, + initialTranslationDetailUIState = initialTranslationDetailUIState, + mantraRepository = mantraRepository, + ) + ) + + when (val translationDetailUIState = translationDetailViewModel.translationDetailUIState) { + is TranslationDetailUIState.Error -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(50.dp)) + Text(text = translationDetailUIState.message) + } + } + + is TranslationDetailUIState.Loaded -> { + val translation = translationDetailUIState.translation + Scaffold( + topBar = { + TopAppBar( + title = { Text(translation.name) }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "Back" + ) + } + }, + ) + } + ) { innerPadding -> + LazyColumn( + modifier = Modifier.padding(innerPadding).fillMaxSize().padding(20.dp), + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + // Details + item { + Text( + text = "Details", + style = MaterialTheme.typography.labelMedium + ) + } + item { + Card { + DetailRow(label = "Name", value = translation.name) + DetailRow(label = "Dialect", value = translation.dialectId) + DetailRow(label = "Visibility", value = translation.visibility) + DetailRow(label = "License", value = translation.license) + DetailRow(label = "Source version", value = translation.artifactVersionId) + DetailRow(label = "Author", value = translation.publicKey) + DetailRow(label = "Created", value = translation.createdAt.toString()) + } + } + + item { HorizontalDivider() } + + // Chapters with translation progress + item { + Text( + text = "Chapters", + style = MaterialTheme.typography.labelMedium + ) + } + if (translationDetailUIState.chapters.isEmpty()) { + item { Text("No chapters.") } + } else { + items( + items = translationDetailUIState.chapters, + key = { progress -> progress.chapter.id } + ) { progress -> + Card { + ListItem( + leadingContent = { + Icon( + Icons.Default.Article, + contentDescription = "Chapter" + ) + }, + headlineContent = { Text("Chapter ${progress.chapter.index}") }, + supportingContent = { + Text("${progress.translatedChunks}/${progress.totalChunks} chunks translated") + } + ) + } + } + } + } + } + } + + TranslationDetailUIState.Loading -> { + Column( + modifier = Modifier.fillMaxWidth().padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "Translation Detail", + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center + ) + LoadingDataIndicator(fillScreen = false) + Spacer(modifier = Modifier.weight(2f)) + } + } + } + + LaunchedEffect(true) { + if (initialTranslationDetailUIState == TranslationDetailUIState.Loading) { + translationDetailViewModel.initiateTranslationDetail() + } + } +} + +@Preview +@Composable +private fun TranslationDetailScreenPreview() { + TorchTheme { + Surface(modifier = Modifier.fillMaxSize()) { + TranslationDetailScreen( + activeUserPublicKey = "", + translationArtifactVersionId = "translationId", + chatRoomId = "chatRoomId", + relayHint = null, + initialTranslationDetailUIState = TranslationDetailUIState.Loaded( + translation = MantraTranslationArtifactVersion( + id = "translationId", + publicKey = "author", + artifactVersionId = "versionId", + dialectId = "dialectId", + name = "Sesotho", + visibility = "private", + license = "cc", + signature = "", + chatRoomId = "chatRoomId" + ) + ), + mantraRepository = MantraRepository.NO_OP_MANTRA_REPOSITORY, + onNavigateBack = {} + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt index 03d39aad..4d8e9c81 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/MantraNavHost.kt @@ -93,10 +93,12 @@ import press.mantra.compose.ui.composable.navigation.routes.AddChapterRoute import press.mantra.compose.ui.composable.navigation.routes.AddTranslationRoute import press.mantra.compose.ui.composable.navigation.routes.ArtifactDetailRoute import press.mantra.compose.ui.composable.navigation.routes.ChapterDetailRoute +import press.mantra.compose.ui.composable.navigation.routes.TranslationDetailRoute import press.mantra.compose.ui.composable.ArtifactDetailScreen import press.mantra.compose.ui.composable.AddChapterScreen import press.mantra.compose.ui.composable.AddTranslationScreen import press.mantra.compose.ui.composable.ChapterDetailScreen +import press.mantra.compose.ui.composable.TranslationDetailScreen @Composable fun MantraNavHost( @@ -716,6 +718,20 @@ fun MantraNavHost( } ) } + composable { backStackEntry -> + val route = backStackEntry.toRoute() + + TranslationDetailScreen( + activeUserPublicKey = route.activeUserPublicKey, + translationArtifactVersionId = route.translationArtifactVersionId, + chatRoomId = route.chatRoomId, + relayHint = route.relayHint, + mantraRepository = databaseMantraRepository, + onNavigateBack = { + navController.popBackStack() + } + ) + } composable { backStackEntry -> val route = backStackEntry.toRoute() diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/TranslationDetailRoute.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/TranslationDetailRoute.kt new file mode 100644 index 00000000..f69b4796 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/TranslationDetailRoute.kt @@ -0,0 +1,11 @@ +package press.mantra.compose.ui.composable.navigation.routes + +import kotlinx.serialization.Serializable + +@Serializable +data class TranslationDetailRoute( + val activeUserPublicKey: String, + val translationArtifactVersionId: String, + val chatRoomId: String, + val relayHint: String? +): Route() diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/DetailRow.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/DetailRow.kt new file mode 100644 index 00000000..942496f5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/widgets/DetailRow.kt @@ -0,0 +1,14 @@ +package press.mantra.compose.ui.composable.widgets + +import androidx.compose.material3.ListItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable + +/** A labelled value row for detail screens (label above, value below). */ +@Composable +fun DetailRow(label: String, value: String) { + ListItem( + overlineContent = { Text(label) }, + headlineContent = { Text(value) } + ) +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/TranslationDetailViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/TranslationDetailViewModel.kt new file mode 100644 index 00000000..1a8429f5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/TranslationDetailViewModel.kt @@ -0,0 +1,82 @@ +package press.mantra.compose.ui.view.model + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import co.touchlab.kermit.Logger +import com.vitorpamplona.quartz.nip01Core.core.HexKey +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.launch +import press.mantra.compose.repository.MantraRepository +import press.mantra.compose.ui.view.state.TranslationChapterProgress +import press.mantra.compose.ui.view.state.TranslationDetailUIState + +class TranslationDetailViewModel( + val translationArtifactVersionId: String, + val chatRoomId: String, + val activeUserPublicKey: HexKey, + val relayHint: String?, + initialTranslationDetailUIState: TranslationDetailUIState, + val mantraRepository: MantraRepository, +): ViewModel() { + + var translationDetailUIState: TranslationDetailUIState by mutableStateOf(initialTranslationDetailUIState) + private set + + private val logger = Logger.withTag(TAG) + + fun initiateTranslationDetail() { + logger.d("initiateTranslationDetail: $translationArtifactVersionId") + viewModelScope.launch(Dispatchers.IO) { + val translation = mantraRepository.getTranslation(translationArtifactVersionId) + + translationDetailUIState = if (translation == null) { + TranslationDetailUIState.Error("Couldn't find the translation") + } else { + val chapters = mantraRepository.getTranslationChapters(translationArtifactVersionId) + .map { chapter -> + val chunks = mantraRepository.getTranslationChunks(chapter.id) + TranslationChapterProgress( + chapter = chapter, + totalChunks = chunks.size, + translatedChunks = chunks.count { it.text.isNotBlank() }, + ) + } + TranslationDetailUIState.Loaded( + translation = translation, + chapters = chapters, + ) + } + } + } + + companion object { + private const val TAG = "TranslationDetailViewModel" + + fun factory( + activeUserPublicKey: HexKey, + translationArtifactVersionId: String, + chatRoomId: String, + relayHint: String?, + initialTranslationDetailUIState: TranslationDetailUIState = TranslationDetailUIState.Loading, + mantraRepository: MantraRepository, + ): ViewModelProvider.Factory = viewModelFactory { + initializer { + TranslationDetailViewModel( + activeUserPublicKey = activeUserPublicKey, + translationArtifactVersionId = translationArtifactVersionId, + chatRoomId = chatRoomId, + relayHint = relayHint, + initialTranslationDetailUIState = initialTranslationDetailUIState, + mantraRepository = mantraRepository, + ) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/TranslationDetailUIState.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/TranslationDetailUIState.kt new file mode 100644 index 00000000..094071a6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/TranslationDetailUIState.kt @@ -0,0 +1,24 @@ +package press.mantra.compose.ui.view.state + +import press.mantra.compose.database.model.MantraTranslationArtifactVersion +import press.mantra.compose.database.model.MantraTranslationChapter + +/** A translation chapter with how many of its chunks have been translated. */ +data class TranslationChapterProgress( + val chapter: MantraTranslationChapter, + val totalChunks: Int, + val translatedChunks: Int, +) + +sealed interface TranslationDetailUIState { + data class Loaded( + val translation: MantraTranslationArtifactVersion, + val chapters: List = emptyList(), + ): TranslationDetailUIState + + data class Error( + val message: String + ): TranslationDetailUIState + + data object Loading: TranslationDetailUIState +}