From d671c7c300df1e84474fed7991e2f409516de120 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Sun, 26 Jul 2026 01:10:22 +0200 Subject: [PATCH] Add ArtifactDetailScreen with chapters and translations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clicking an artifact in a chat room's library now opens an artifact detail screen instead of the placeholder route. - New ArtifactDetailRoute + ArtifactDetailScreen/ViewModel/UIState. The screen shows the artifact's details (name, url, visibility, license, dialect, author, created), its versions, and — walked via the artifact's versions — the associated chapters and translations, each with an empty state. - DAO queries (validated by Room): getArtifactById, versions by artifactId, chapters by artifactId (Chapter JOIN ArtifactVersion, ordered by index), and translations by artifactId (TranslationArtifactVersion JOIN ArtifactVersion). Exposed via MantraRepository (impl + NO_OP). - ChatRoomDetailScreen navigates to ArtifactDetailRoute on artifact click; MantraNavHost registers the route with a back-stack pop. Co-Authored-By: Claude Opus 4.8 --- .../compose/database/dao/MantraArtifactDao.kt | 3 + .../database/dao/MantraArtifactVersionDao.kt | 4 + .../compose/database/dao/MantraChapterDao.kt | 13 + .../MantraTranslationArtifactVersionDao.kt | 13 + .../repository/DatabaseMantraRepository.kt | 14 + .../compose/repository/MantraRepository.kt | 19 ++ .../ui/composable/ArtifactDetailScreen.kt | 268 ++++++++++++++++++ .../ui/composable/ChatRoomDetailScreen.kt | 8 +- .../ui/composable/navigation/MantraNavHost.kt | 16 ++ .../navigation/routes/ArtifactDetailRoute.kt | 11 + .../ui/view/model/ArtifactDetailViewModel.kt | 74 +++++ .../ui/view/state/ArtifactDetailUIState.kt | 21 ++ 12 files changed, 462 insertions(+), 2 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/ArtifactDetailRoute.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ArtifactDetailViewModel.kt create mode 100644 composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/ArtifactDetailUIState.kt diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactDao.kt index 33f11ca3..b9cc1758 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactDao.kt @@ -12,4 +12,7 @@ interface MantraArtifactDao { @Query("SELECT * FROM MantraArtifact WHERE chatRoomId = :chatRoomId ORDER BY createdAt DESC") suspend fun getArtifactsByChatRoomId(chatRoomId: String): List + + @Query("SELECT * FROM MantraArtifact WHERE id = :id") + suspend fun getArtifactById(id: String): MantraArtifact? } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactVersionDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactVersionDao.kt index 6b13083e..edfc7861 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactVersionDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraArtifactVersionDao.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.MantraArtifactVersion @@ -10,4 +11,7 @@ import press.mantra.compose.database.model.MantraArtifactVersion interface MantraArtifactVersionDao { @Upsert suspend fun upsert(mantraArtifactVersion: MantraArtifactVersion) + + @Query("SELECT * FROM MantraArtifactVersion WHERE artifactId = :artifactId ORDER BY createdAt DESC") + suspend fun getArtifactVersionsByArtifactId(artifactId: String): List } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraChapterDao.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraChapterDao.kt index 5ad89439..7e0aa5d9 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraChapterDao.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/database/dao/MantraChapterDao.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.MantraChapter @@ -10,4 +11,16 @@ import press.mantra.compose.database.model.MantraChapter interface MantraChapterDao { @Upsert suspend fun upsert(mantraChapter: MantraChapter) + + /** Chapters across all versions of an artifact, ordered by their in-book index. */ + @Query( + """ + SELECT MantraChapter.* FROM MantraChapter + INNER JOIN MantraArtifactVersion + ON MantraChapter.artifactVersionId = MantraArtifactVersion.id + WHERE MantraArtifactVersion.artifactId = :artifactId + ORDER BY MantraChapter.`index` ASC + """ + ) + suspend fun getChaptersByArtifactId(artifactId: String): List } \ No newline at end of file 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 5f467aa0..8ef09237 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 @@ -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.MantraTranslationArtifactVersion @@ -10,4 +11,16 @@ import press.mantra.compose.database.model.MantraTranslationArtifactVersion interface MantraTranslationArtifactVersionDao { @Upsert suspend fun upsert(mantraTranslationArtifactVersion: MantraTranslationArtifactVersion) + + /** Translations targeting any version of an artifact. */ + @Query( + """ + SELECT MantraTranslationArtifactVersion.* FROM MantraTranslationArtifactVersion + INNER JOIN MantraArtifactVersion + ON MantraTranslationArtifactVersion.artifactVersionId = MantraArtifactVersion.id + WHERE MantraArtifactVersion.artifactId = :artifactId + ORDER BY MantraTranslationArtifactVersion.createdAt DESC + """ + ) + suspend fun getTranslationsByArtifactId(artifactId: 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 fb92dd86..8121b3d6 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 @@ -6,7 +6,9 @@ import kotlinx.coroutines.CoroutineScope import press.mantra.compose.database.MantraDatabase import press.mantra.compose.database.model.MantraArtifact import press.mantra.compose.database.model.MantraArtifactVersion +import press.mantra.compose.database.model.MantraChapter import press.mantra.compose.database.model.MantraDialect +import press.mantra.compose.database.model.MantraTranslationArtifactVersion import press.mantra.compose.database.model.MarmotInnerEvent import press.mantra.compose.nostr.nip30303.ArtifactEvent import press.mantra.compose.nostr.nip30303.ArtifactVersionEvent @@ -24,6 +26,18 @@ class DatabaseMantraRepository( override suspend fun getArtifacts(chatRoomId: String): List = database.mantraArtifactDao().getArtifactsByChatRoomId(chatRoomId) + override suspend fun getArtifact(id: String): MantraArtifact? = + database.mantraArtifactDao().getArtifactById(id) + + override suspend fun getArtifactVersions(artifactId: String): List = + database.mantraArtifactVersionDao().getArtifactVersionsByArtifactId(artifactId) + + override suspend fun getChaptersForArtifact(artifactId: String): List = + database.mantraChapterDao().getChaptersByArtifactId(artifactId) + + override suspend fun getTranslationsForArtifact(artifactId: String): List = + database.mantraTranslationArtifactVersionDao().getTranslationsByArtifactId(artifactId) + override suspend fun getDialects(chatRoomId: String): List = database.mantraDialectDao().getDialectsByChatRoomId(chatRoomId) 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 7f74b788..65597fff 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/repository/MantraRepository.kt @@ -2,12 +2,23 @@ package press.mantra.compose.repository import com.vitorpamplona.quartz.nip01Core.core.HexKey import press.mantra.compose.database.model.MantraArtifact +import press.mantra.compose.database.model.MantraArtifactVersion +import press.mantra.compose.database.model.MantraChapter import press.mantra.compose.database.model.MantraDialect +import press.mantra.compose.database.model.MantraTranslationArtifactVersion import press.mantra.compose.database.model.MarmotInnerEvent interface MantraRepository { suspend fun getArtifacts(chatRoomId: String): List + suspend fun getArtifact(id: String): MantraArtifact? + + suspend fun getArtifactVersions(artifactId: String): List + + suspend fun getChaptersForArtifact(artifactId: String): List + + suspend fun getTranslationsForArtifact(artifactId: String): List + suspend fun getDialects(chatRoomId: String): List suspend fun addDialect( @@ -43,6 +54,14 @@ interface MantraRepository { val NO_OP_MANTRA_REPOSITORY = object: MantraRepository{ override suspend fun getArtifacts(chatRoomId: String): List = emptyList() + override suspend fun getArtifact(id: String): MantraArtifact? = null + + override suspend fun getArtifactVersions(artifactId: String): List = emptyList() + + override suspend fun getChaptersForArtifact(artifactId: String): List = emptyList() + + override suspend fun getTranslationsForArtifact(artifactId: String): List = emptyList() + override suspend fun getDialects(chatRoomId: String): List = emptyList() override suspend fun addDialect( 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 new file mode 100644 index 00000000..1e15a49f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ArtifactDetailScreen.kt @@ -0,0 +1,268 @@ +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.material.icons.filled.Translate +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.MantraArtifact +import press.mantra.compose.repository.MantraRepository +import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator +import press.mantra.compose.ui.theme.TorchTheme +import press.mantra.compose.ui.view.model.ArtifactDetailViewModel +import press.mantra.compose.ui.view.state.ArtifactDetailUIState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ArtifactDetailScreen( + activeUserPublicKey: HexKey, + artifactId: String, + chatRoomId: String, + relayHint: String?, + initialArtifactDetailUIState: ArtifactDetailUIState = ArtifactDetailUIState.Loading, + mantraRepository: MantraRepository, + onNavigateBack: () -> Unit, +) { + val artifactDetailViewModel: ArtifactDetailViewModel = viewModel( + factory = ArtifactDetailViewModel.factory( + activeUserPublicKey = activeUserPublicKey, + artifactId = artifactId, + chatRoomId = chatRoomId, + relayHint = relayHint, + initialArtifactDetailUIState = initialArtifactDetailUIState, + mantraRepository = mantraRepository, + ) + ) + + when (val artifactDetailUIState = artifactDetailViewModel.artifactDetailUIState) { + is ArtifactDetailUIState.Error -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(50.dp)) + Text(text = artifactDetailUIState.message) + } + } + + is ArtifactDetailUIState.Loaded -> { + val artifact = artifactDetailUIState.artifact + Scaffold( + topBar = { + TopAppBar( + title = { Text(artifact.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 = artifact.name) + DetailRow(label = "Url", value = artifact.url) + DetailRow(label = "Visibility", value = artifact.visibility) + DetailRow(label = "License", value = artifact.license) + DetailRow(label = "Dialect", value = artifact.dialectId) + DetailRow(label = "Author", value = artifact.publicKey) + DetailRow(label = "Created", value = artifact.createdAt.toString()) + } + } + + // Versions + item { + Text( + text = "Versions", + style = MaterialTheme.typography.labelMedium + ) + } + if (artifactDetailUIState.versions.isEmpty()) { + item { Text("No versions.") } + } else { + items( + items = artifactDetailUIState.versions, + key = { version -> version.id } + ) { version -> + Card { + ListItem( + headlineContent = { Text(version.versionLabel) }, + overlineContent = { Text("Version") } + ) + } + } + } + + item { HorizontalDivider() } + + // Chapters + item { + Text( + text = "Chapters", + style = MaterialTheme.typography.labelMedium + ) + } + if (artifactDetailUIState.chapters.isEmpty()) { + item { Text("No chapters yet.") } + } else { + items( + items = artifactDetailUIState.chapters, + key = { chapter -> chapter.id } + ) { chapter -> + Card { + ListItem( + leadingContent = { + Icon( + Icons.Default.Article, + contentDescription = "Chapter" + ) + }, + overlineContent = { Text("Chapter ${chapter.index}") }, + headlineContent = { Text(chapter.name) }, + supportingContent = { + Text("${chapter.wordCount} words · ${chapter.characterCount} characters") + } + ) + } + } + } + + item { HorizontalDivider() } + + // Translations + item { + Text( + text = "Translations", + style = MaterialTheme.typography.labelMedium + ) + } + if (artifactDetailUIState.translations.isEmpty()) { + item { Text("No translations yet.") } + } else { + items( + items = artifactDetailUIState.translations, + key = { translation -> translation.id } + ) { translation -> + Card { + ListItem( + leadingContent = { + Icon( + Icons.Default.Translate, + contentDescription = "Translation" + ) + }, + headlineContent = { Text(translation.name) }, + supportingContent = { Text(translation.visibility) } + ) + } + } + } + } + } + } + + ArtifactDetailUIState.Loading -> { + Column( + modifier = Modifier.fillMaxWidth().padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + Spacer(modifier = Modifier.weight(1f)) + Text( + text = "Artifact Detail", + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center + ) + LoadingDataIndicator(fillScreen = false) + Spacer(modifier = Modifier.weight(2f)) + } + } + } + + LaunchedEffect(true) { + if (initialArtifactDetailUIState == ArtifactDetailUIState.Loading) { + artifactDetailViewModel.initiateArtifactDetail() + } + } +} + +@Composable +private fun DetailRow(label: String, value: String) { + ListItem( + overlineContent = { Text(label) }, + headlineContent = { Text(value) } + ) +} + +@Preview +@Composable +private fun ArtifactDetailScreenPreview() { + TorchTheme { + Surface(modifier = Modifier.fillMaxSize()) { + ArtifactDetailScreen( + activeUserPublicKey = "", + artifactId = "artifactId", + chatRoomId = "chatRoomId", + relayHint = null, + initialArtifactDetailUIState = ArtifactDetailUIState.Loaded( + artifact = MantraArtifact( + id = "artifactId", + publicKey = "author", + name = "To Kill a Mockingbird", + url = "https://harper.com/2-kill-Bird", + visibility = "private", + dialectId = "dialectId", + license = "cc", + chatRoomId = "chatRoomId", + signature = "" + ) + ), + mantraRepository = MantraRepository.NO_OP_MANTRA_REPOSITORY, + onNavigateBack = {} + ) + } + } +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt index 5c9b0468..1af91af6 100644 --- a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/ChatRoomDetailScreen.kt @@ -58,6 +58,7 @@ import press.mantra.compose.ui.view.model.ChatRoomDetailViewModel import press.mantra.compose.ui.view.state.ChatRoomDetailUIState import com.vitorpamplona.quartz.nip01Core.core.HexKey import press.mantra.compose.ui.composable.navigation.routes.AddArtifactRoute +import press.mantra.compose.ui.composable.navigation.routes.ArtifactDetailRoute @OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class) @Composable @@ -150,8 +151,11 @@ fun ChatRoomDetailScreen( Card( onClick = { onNavigateToRoute.invoke( - ImplementationPendingRoute( - "Artifact detail" + ArtifactDetailRoute( + activeUserPublicKey = activeUserPublicKey, + artifactId = artifact.id, + chatRoomId = chatRoomId, + relayHint = relayHint ) ) } 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 91680106..9b0d521b 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 @@ -89,6 +89,8 @@ import kotlinx.coroutines.launch import press.mantra.compose.database.repository.DatabaseMantraRepository import press.mantra.compose.ui.composable.AddArtifactScreen import press.mantra.compose.ui.composable.navigation.routes.AddArtifactRoute +import press.mantra.compose.ui.composable.navigation.routes.ArtifactDetailRoute +import press.mantra.compose.ui.composable.ArtifactDetailScreen @Composable fun MantraNavHost( @@ -675,6 +677,20 @@ fun MantraNavHost( } ) } + composable { backStackEntry -> + val route = backStackEntry.toRoute() + + ArtifactDetailScreen( + activeUserPublicKey = route.activeUserPublicKey, + artifactId = route.artifactId, + 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/ArtifactDetailRoute.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/ArtifactDetailRoute.kt new file mode 100644 index 00000000..393c8d70 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/composable/navigation/routes/ArtifactDetailRoute.kt @@ -0,0 +1,11 @@ +package press.mantra.compose.ui.composable.navigation.routes + +import kotlinx.serialization.Serializable + +@Serializable +data class ArtifactDetailRoute( + val activeUserPublicKey: String, + val artifactId: String, + val chatRoomId: String, + val relayHint: String? +): Route() diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ArtifactDetailViewModel.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ArtifactDetailViewModel.kt new file mode 100644 index 00000000..bc1086a9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/model/ArtifactDetailViewModel.kt @@ -0,0 +1,74 @@ +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.ArtifactDetailUIState + +class ArtifactDetailViewModel( + val artifactId: String, + val chatRoomId: String, + val activeUserPublicKey: HexKey, + val relayHint: String?, + initialArtifactDetailUIState: ArtifactDetailUIState, + val mantraRepository: MantraRepository, +): ViewModel() { + + var artifactDetailUIState: ArtifactDetailUIState by mutableStateOf(initialArtifactDetailUIState) + private set + + private val logger = Logger.withTag(TAG) + + fun initiateArtifactDetail() { + logger.d("initiateArtifactDetail: $artifactId") + viewModelScope.launch(Dispatchers.IO) { + val artifact = mantraRepository.getArtifact(artifactId) + + artifactDetailUIState = if (artifact == null) { + ArtifactDetailUIState.Error("Couldn't find the artifact") + } else { + ArtifactDetailUIState.Loaded( + artifact = artifact, + versions = mantraRepository.getArtifactVersions(artifactId), + chapters = mantraRepository.getChaptersForArtifact(artifactId), + translations = mantraRepository.getTranslationsForArtifact(artifactId), + ) + } + } + } + + companion object { + private const val TAG = "ArtifactDetailViewModel" + + fun factory( + activeUserPublicKey: HexKey, + artifactId: String, + chatRoomId: String, + relayHint: String?, + initialArtifactDetailUIState: ArtifactDetailUIState = ArtifactDetailUIState.Loading, + mantraRepository: MantraRepository, + ): ViewModelProvider.Factory = viewModelFactory { + initializer { + ArtifactDetailViewModel( + activeUserPublicKey = activeUserPublicKey, + artifactId = artifactId, + chatRoomId = chatRoomId, + relayHint = relayHint, + initialArtifactDetailUIState = initialArtifactDetailUIState, + mantraRepository = mantraRepository, + ) + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/ArtifactDetailUIState.kt b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/ArtifactDetailUIState.kt new file mode 100644 index 00000000..f390abf9 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/press/mantra/compose/ui/view/state/ArtifactDetailUIState.kt @@ -0,0 +1,21 @@ +package press.mantra.compose.ui.view.state + +import press.mantra.compose.database.model.MantraArtifact +import press.mantra.compose.database.model.MantraArtifactVersion +import press.mantra.compose.database.model.MantraChapter +import press.mantra.compose.database.model.MantraTranslationArtifactVersion + +sealed interface ArtifactDetailUIState { + data class Loaded( + val artifact: MantraArtifact, + val versions: List = emptyList(), + val chapters: List = emptyList(), + val translations: List = emptyList(), + ): ArtifactDetailUIState + + data class Error( + val message: String + ): ArtifactDetailUIState + + data object Loading: ArtifactDetailUIState +}