Merge branch 'claude/admiring-mahavira-a862d1' into artifacts
This commit is contained in:
commit
05e200319a
13 files changed
+407
-11
No files matched your search
@@ -14,4 +14,7 @@ interface MantraDialectDao {
|
|||||||
|
|
||||||
@Query("SELECT * FROM MantraDialect WHERE chatRoomId = :chatRoomId ORDER BY name")
|
@Query("SELECT * FROM MantraDialect WHERE chatRoomId = :chatRoomId ORDER BY name")
|
||||||
suspend fun getDialectsByChatRoomId(chatRoomId: String): List<MantraDialect>
|
suspend fun getDialectsByChatRoomId(chatRoomId: String): List<MantraDialect>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM MantraDialect WHERE id = :id")
|
||||||
|
suspend fun getDialectById(id: String): MantraDialect?
|
||||||
}
|
}
|
||||||
+3
@@ -23,4 +23,7 @@ interface MantraTranslationArtifactVersionDao {
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
suspend fun getTranslationsByArtifactId(artifactId: String): List<MantraTranslationArtifactVersion>
|
suspend fun getTranslationsByArtifactId(artifactId: String): List<MantraTranslationArtifactVersion>
|
||||||
|
|
||||||
|
@Query("SELECT * FROM MantraTranslationArtifactVersion WHERE id = :id")
|
||||||
|
suspend fun getTranslationById(id: String): MantraTranslationArtifactVersion?
|
||||||
}
|
}
|
||||||
+4
@@ -3,6 +3,7 @@ package press.mantra.compose.database.dao
|
|||||||
import androidx.room3.Dao
|
import androidx.room3.Dao
|
||||||
import androidx.room3.Insert
|
import androidx.room3.Insert
|
||||||
import androidx.room3.OnConflictStrategy.Companion.IGNORE
|
import androidx.room3.OnConflictStrategy.Companion.IGNORE
|
||||||
|
import androidx.room3.Query
|
||||||
import androidx.room3.Upsert
|
import androidx.room3.Upsert
|
||||||
import press.mantra.compose.database.model.MantraTranslationChapter
|
import press.mantra.compose.database.model.MantraTranslationChapter
|
||||||
|
|
||||||
@@ -10,4 +11,7 @@ import press.mantra.compose.database.model.MantraTranslationChapter
|
|||||||
interface MantraTranslationChapterDao {
|
interface MantraTranslationChapterDao {
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun upsert(mantraTranslationChapter: MantraTranslationChapter)
|
suspend fun upsert(mantraTranslationChapter: MantraTranslationChapter)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM MantraTranslationChapter WHERE translationArtifactVersionId = :translationArtifactVersionId ORDER BY `index` ASC")
|
||||||
|
suspend fun getTranslationChaptersByTranslationArtifactVersionId(translationArtifactVersionId: String): List<MantraTranslationChapter>
|
||||||
}
|
}
|
||||||
+4
@@ -3,6 +3,7 @@ package press.mantra.compose.database.dao
|
|||||||
import androidx.room3.Dao
|
import androidx.room3.Dao
|
||||||
import androidx.room3.Insert
|
import androidx.room3.Insert
|
||||||
import androidx.room3.OnConflictStrategy.Companion.IGNORE
|
import androidx.room3.OnConflictStrategy.Companion.IGNORE
|
||||||
|
import androidx.room3.Query
|
||||||
import androidx.room3.Upsert
|
import androidx.room3.Upsert
|
||||||
import press.mantra.compose.database.model.MantraTranslationChunk
|
import press.mantra.compose.database.model.MantraTranslationChunk
|
||||||
|
|
||||||
@@ -10,4 +11,7 @@ import press.mantra.compose.database.model.MantraTranslationChunk
|
|||||||
interface MantraTranslationChunkDao {
|
interface MantraTranslationChunkDao {
|
||||||
@Upsert
|
@Upsert
|
||||||
suspend fun upsert(mantraTranslationChunk: MantraTranslationChunk)
|
suspend fun upsert(mantraTranslationChunk: MantraTranslationChunk)
|
||||||
|
|
||||||
|
@Query("SELECT * FROM MantraTranslationChunk WHERE translationChapterId = :translationChapterId ORDER BY `index` ASC")
|
||||||
|
suspend fun getTranslationChunksByTranslationChapterId(translationChapterId: String): List<MantraTranslationChunk>
|
||||||
}
|
}
|
||||||
+13
-2
@@ -54,12 +54,14 @@ class DatabaseMantraRepository(
|
|||||||
val version = database.mantraArtifactVersionDao()
|
val version = database.mantraArtifactVersionDao()
|
||||||
.getArtifactVersionsByArtifactId(artifactId)
|
.getArtifactVersionsByArtifactId(artifactId)
|
||||||
.firstOrNull() ?: return null
|
.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(
|
val translationVersionTemplate = TranslationArtifactVersionEvent.build(
|
||||||
artifactVersionId = version.id,
|
artifactVersionId = version.id,
|
||||||
dialectId = dialectId,
|
dialectId = dialectId,
|
||||||
name = artifact.name,
|
name = dialect.name,
|
||||||
visibility = artifact.visibility,
|
visibility = artifact.visibility,
|
||||||
license = artifact.license,
|
license = artifact.license,
|
||||||
)
|
)
|
||||||
@@ -156,6 +158,15 @@ class DatabaseMantraRepository(
|
|||||||
override suspend fun getTranslationsForArtifact(artifactId: String): List<MantraTranslationArtifactVersion> =
|
override suspend fun getTranslationsForArtifact(artifactId: String): List<MantraTranslationArtifactVersion> =
|
||||||
database.mantraTranslationArtifactVersionDao().getTranslationsByArtifactId(artifactId)
|
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(
|
override suspend fun addChapter(
|
||||||
artifactId: String,
|
artifactId: String,
|
||||||
name: 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.MantraChunk
|
||||||
import press.mantra.compose.database.model.MantraDialect
|
import press.mantra.compose.database.model.MantraDialect
|
||||||
import press.mantra.compose.database.model.MantraTranslationArtifactVersion
|
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
|
import press.mantra.compose.database.model.MarmotInnerEvent
|
||||||
|
|
||||||
interface MantraRepository {
|
interface MantraRepository {
|
||||||
@@ -24,6 +26,12 @@ interface MantraRepository {
|
|||||||
|
|
||||||
suspend fun getTranslationsForArtifact(artifactId: String): List<MantraTranslationArtifactVersion>
|
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
|
* Start a translation of the artifact into the given dialect: creates a
|
||||||
* MantraTranslationArtifactVersion for the artifact's latest version, and
|
* 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 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(
|
override suspend fun addTranslation(
|
||||||
artifactId: String,
|
artifactId: String,
|
||||||
dialectId: String,
|
dialectId: String,
|
||||||
|
|||||||
+14
-9
@@ -38,10 +38,12 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import press.mantra.compose.database.model.MantraArtifact
|
import press.mantra.compose.database.model.MantraArtifact
|
||||||
import press.mantra.compose.repository.MantraRepository
|
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.AddChapterRoute
|
||||||
import press.mantra.compose.ui.composable.navigation.routes.AddTranslationRoute
|
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.ChapterDetailRoute
|
||||||
import press.mantra.compose.ui.composable.navigation.routes.Route
|
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.composable.widgets.LoadingDataIndicator
|
||||||
import press.mantra.compose.ui.theme.TorchTheme
|
import press.mantra.compose.ui.theme.TorchTheme
|
||||||
import press.mantra.compose.ui.view.model.ArtifactDetailViewModel
|
import press.mantra.compose.ui.view.model.ArtifactDetailViewModel
|
||||||
@@ -228,7 +230,18 @@ fun ArtifactDetailScreen(
|
|||||||
items = artifactDetailUIState.translations,
|
items = artifactDetailUIState.translations,
|
||||||
key = { translation -> translation.id }
|
key = { translation -> translation.id }
|
||||||
) { translation ->
|
) { translation ->
|
||||||
Card {
|
Card(
|
||||||
|
onClick = {
|
||||||
|
onNavigateToRoute.invoke(
|
||||||
|
TranslationDetailRoute(
|
||||||
|
activeUserPublicKey = activeUserPublicKey,
|
||||||
|
translationArtifactVersionId = translation.id,
|
||||||
|
chatRoomId = chatRoomId,
|
||||||
|
relayHint = relayHint
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -294,14 +307,6 @@ fun ArtifactDetailScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DetailRow(label: String, value: String) {
|
|
||||||
ListItem(
|
|
||||||
overlineContent = { Text(label) },
|
|
||||||
headlineContent = { Text(value) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun ArtifactDetailScreenPreview() {
|
private fun ArtifactDetailScreenPreview() {
|
||||||
|
|||||||
+205
@@ -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 = {}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+16
@@ -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.AddTranslationRoute
|
||||||
import press.mantra.compose.ui.composable.navigation.routes.ArtifactDetailRoute
|
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.ChapterDetailRoute
|
||||||
|
import press.mantra.compose.ui.composable.navigation.routes.TranslationDetailRoute
|
||||||
import press.mantra.compose.ui.composable.ArtifactDetailScreen
|
import press.mantra.compose.ui.composable.ArtifactDetailScreen
|
||||||
import press.mantra.compose.ui.composable.AddChapterScreen
|
import press.mantra.compose.ui.composable.AddChapterScreen
|
||||||
import press.mantra.compose.ui.composable.AddTranslationScreen
|
import press.mantra.compose.ui.composable.AddTranslationScreen
|
||||||
import press.mantra.compose.ui.composable.ChapterDetailScreen
|
import press.mantra.compose.ui.composable.ChapterDetailScreen
|
||||||
|
import press.mantra.compose.ui.composable.TranslationDetailScreen
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MantraNavHost(
|
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 ->
|
composable<AddTranslationRoute> { backStackEntry ->
|
||||||
val route = backStackEntry.toRoute<AddTranslationRoute>()
|
val route = backStackEntry.toRoute<AddTranslationRoute>()
|
||||||
|
|
||||||
|
|||||||
+11
@@ -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()
|
||||||
+14
@@ -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) }
|
||||||
|
)
|
||||||
|
}
|
||||||
+82
@@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
@@ -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