Merge branch 'claude/admiring-mahavira-a862d1' into artifacts
This commit is contained in:
@@ -14,4 +14,7 @@ interface MantraDialectDao {
|
||||
|
||||
@Query("SELECT * FROM MantraDialect WHERE chatRoomId = :chatRoomId ORDER BY name")
|
||||
suspend fun getDialectsByChatRoomId(chatRoomId: String): List<MantraDialect>
|
||||
|
||||
@Query("SELECT * FROM MantraDialect WHERE id = :id")
|
||||
suspend fun getDialectById(id: String): MantraDialect?
|
||||
}
|
||||
@@ -23,4 +23,7 @@ interface MantraTranslationArtifactVersionDao {
|
||||
"""
|
||||
)
|
||||
suspend fun getTranslationsByArtifactId(artifactId: String): List<MantraTranslationArtifactVersion>
|
||||
|
||||
@Query("SELECT * FROM MantraTranslationArtifactVersion WHERE id = :id")
|
||||
suspend fun getTranslationById(id: String): MantraTranslationArtifactVersion?
|
||||
}
|
||||
@@ -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<MantraTranslationChapter>
|
||||
}
|
||||
@@ -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<MantraTranslationChunk>
|
||||
}
|
||||
@@ -54,12 +54,14 @@ class DatabaseMantraRepository(
|
||||
val version = database.mantraArtifactVersionDao()
|
||||
.getArtifactVersionsByArtifactId(artifactId)
|
||||
.firstOrNull() ?: return null
|
||||
val dialect = database.mantraDialectDao().getDialectById(dialectId) ?: return null
|
||||
|
||||
// The translation inherits the source artifact's name/visibility/license.
|
||||
// The translation is named after its dialect, and inherits the source
|
||||
// artifact's visibility/license.
|
||||
val translationVersionTemplate = TranslationArtifactVersionEvent.build(
|
||||
artifactVersionId = version.id,
|
||||
dialectId = dialectId,
|
||||
name = artifact.name,
|
||||
name = dialect.name,
|
||||
visibility = artifact.visibility,
|
||||
license = artifact.license,
|
||||
)
|
||||
@@ -156,6 +158,15 @@ class DatabaseMantraRepository(
|
||||
override suspend fun getTranslationsForArtifact(artifactId: String): List<MantraTranslationArtifactVersion> =
|
||||
database.mantraTranslationArtifactVersionDao().getTranslationsByArtifactId(artifactId)
|
||||
|
||||
override suspend fun getTranslation(id: String): MantraTranslationArtifactVersion? =
|
||||
database.mantraTranslationArtifactVersionDao().getTranslationById(id)
|
||||
|
||||
override suspend fun getTranslationChapters(translationArtifactVersionId: String): List<MantraTranslationChapter> =
|
||||
database.mantraTranslationChapterDao().getTranslationChaptersByTranslationArtifactVersionId(translationArtifactVersionId)
|
||||
|
||||
override suspend fun getTranslationChunks(translationChapterId: String): List<MantraTranslationChunk> =
|
||||
database.mantraTranslationChunkDao().getTranslationChunksByTranslationChapterId(translationChapterId)
|
||||
|
||||
override suspend fun addChapter(
|
||||
artifactId: String,
|
||||
name: String,
|
||||
|
||||
@@ -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<MantraTranslationArtifactVersion>
|
||||
|
||||
suspend fun getTranslation(id: String): MantraTranslationArtifactVersion?
|
||||
|
||||
suspend fun getTranslationChapters(translationArtifactVersionId: String): List<MantraTranslationChapter>
|
||||
|
||||
suspend fun getTranslationChunks(translationChapterId: String): List<MantraTranslationChunk>
|
||||
|
||||
/**
|
||||
* 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<MantraTranslationArtifactVersion> = emptyList()
|
||||
|
||||
override suspend fun getTranslation(id: String): MantraTranslationArtifactVersion? = null
|
||||
|
||||
override suspend fun getTranslationChapters(translationArtifactVersionId: String): List<MantraTranslationChapter> = emptyList()
|
||||
|
||||
override suspend fun getTranslationChunks(translationChapterId: String): List<MantraTranslationChunk> = emptyList()
|
||||
|
||||
override suspend fun addTranslation(
|
||||
artifactId: String,
|
||||
dialectId: String,
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TranslationDetailRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<TranslationDetailRoute>()
|
||||
|
||||
TranslationDetailScreen(
|
||||
activeUserPublicKey = route.activeUserPublicKey,
|
||||
translationArtifactVersionId = route.translationArtifactVersionId,
|
||||
chatRoomId = route.chatRoomId,
|
||||
relayHint = route.relayHint,
|
||||
mantraRepository = databaseMantraRepository,
|
||||
onNavigateBack = {
|
||||
navController.popBackStack()
|
||||
}
|
||||
)
|
||||
}
|
||||
composable<AddTranslationRoute> { backStackEntry ->
|
||||
val route = backStackEntry.toRoute<AddTranslationRoute>()
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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) }
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<TranslationChapterProgress> = emptyList(),
|
||||
): TranslationDetailUIState
|
||||
|
||||
data class Error(
|
||||
val message: String
|
||||
): TranslationDetailUIState
|
||||
|
||||
data object Loading: TranslationDetailUIState
|
||||
}
|
||||
Reference in New Issue
Block a user