From 302d6c620a70ab2a12a7f093d33321b31efefc52 Mon Sep 17 00:00:00 2001 From: Kgothatso Ngako Date: Mon, 20 Apr 2026 17:17:43 +0200 Subject: [PATCH] Add profile details --- .../ui/composable/NostrEventDetailScreen.kt | 5 + .../ui/composable/SearchResultScreen.kt | 136 +++++----- .../ui/composable/navigation/AuxNavHost.kt | 5 + .../widgets/detail/MetadataEventDetail.kt | 171 +++++++++--- .../model/MetadataEventDetailViewModel.kt | 256 ++++++++++++++++++ 5 files changed, 463 insertions(+), 110 deletions(-) create mode 100755 composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/MetadataEventDetailViewModel.kt diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/NostrEventDetailScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/NostrEventDetailScreen.kt index 630d27e8..f08549f0 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/NostrEventDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/NostrEventDetailScreen.kt @@ -4,6 +4,7 @@ import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.intermdiate.LocalNostrEvent import ac.aux.compose.repository.NostrRepository +import ac.aux.compose.ui.composable.navigation.routes.Route import ac.aux.compose.ui.composable.widgets.LoadingDataIndicator import ac.aux.compose.ui.composable.widgets.detail.MetadataEventDetail import ac.aux.compose.ui.composable.widgets.detail.TextNoteEventDetail @@ -37,6 +38,7 @@ fun NostrEventDetailScreen( nostrEventId: HexKey, nostrRepository: NostrRepository, onNavigateBack: () -> Unit, + onNavigateToEvent: (Route) -> Unit, onNavigateToWriteAReply: (String) -> Unit ) { val nostrEventDetailViewModel: NostrEventDetailViewModel = viewModel( @@ -64,6 +66,8 @@ fun NostrEventDetailScreen( MetadataEventDetail( localNostrEvent = feedListUIState.localNostrEvent, onNavigateBack = onNavigateBack, + onNavigateToEvent = onNavigateToEvent, + nostrRepository = nostrRepository ) } TextNoteEvent.KIND -> { @@ -155,6 +159,7 @@ It has survived not only five centuries, but also the leap into electronic types nostrEventId = "", nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, onNavigateBack = {}, + onNavigateToEvent = {}, onNavigateToWriteAReply = {} ) } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchResultScreen.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchResultScreen.kt index 8f4f9cd1..3a6b5e18 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchResultScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/SearchResultScreen.kt @@ -206,37 +206,72 @@ fun SearchResultScreen( Text("Posts") } SearchResultType.Articles -> { - Text("Posts") + Text("Articles") } SearchResultType.Profiles -> { - Text("Posts") + Text("Profiles") } SearchResultType.Photos -> { - - Text("Posts") + Text("Photos") } SearchResultType.Videos -> { - - Text("Posts") + Text("Videos") } } when (val searchResultListUIState = searchViewModel.searchResultListUIState) { - SearchResultListUIState.Error -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer( - modifier = Modifier.height(50.dp) - ) - Text( - text = "Something went wrong", - ) + SearchResultListUIState.Error -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer( + modifier = Modifier.height(50.dp) + ) + Text( + text = "Something went wrong", + ) + } } - } - is SearchResultListUIState.Loaded -> { - if (searchResultListUIState.localNostrEvents.isEmpty()) { + is SearchResultListUIState.Loaded -> { + if (searchResultListUIState.localNostrEvents.isEmpty()) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer( + modifier = Modifier.height(50.dp) + ) + Text( + text = "No events were found", + ) + } + } else { + + LazyColumn( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items( + items = searchResultListUIState.localNostrEvents, + key = { localNostrEvent -> localNostrEvent.nostrEvent.id} + ) { localNostrEvent -> + localNostrEvent.ListView( + onNavigateToEvent = { nostrEventId -> + onNavigateToEvent.invoke( + NostrEventDetailRoute( + nostrEventId = nostrEventId + ) + ) + } + ) + } + } + } + + } + SearchResultListUIState.Loading -> { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally @@ -245,66 +280,19 @@ fun SearchResultScreen( modifier = Modifier.height(50.dp) ) Text( - text = "No events were found", + text = "Searching for ${searchViewModel.searchResultType.name.lowercase()} on \"${searchQuery.lowercase()}\"", + textAlign = TextAlign.Center ) + Spacer( + modifier = Modifier.height(50.dp) + ) + + LoadingIndicator() } - } else { - - LazyColumn( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.spacedBy(10.dp) - ) { - items( - items = searchResultListUIState.localNostrEvents, - key = { localNostrEvent -> localNostrEvent.nostrEvent.id} - ) { localNostrEvent -> - localNostrEvent.ListView( - onNavigateToEvent = { nostrEventId -> - onNavigateToEvent.invoke( - NostrEventDetailRoute( - nostrEventId = nostrEventId - ) - ) - } - ) - } - } - } - - } - SearchResultListUIState.Loading -> { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Spacer( - modifier = Modifier.height(50.dp) - ) - Text( - text = "Searching for ${searchViewModel.searchResultType.name.lowercase()} on \"${searchQuery.lowercase()}\"", - textAlign = TextAlign.Center - ) - Spacer( - modifier = Modifier.height(50.dp) - ) - - LoadingIndicator() } } } - } - - - - - } - - - - - } } } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt index 9493a208..4e62c7d9 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/navigation/AuxNavHost.kt @@ -376,6 +376,11 @@ fun AuxNavHost( onNavigateBack = { navController.popBackStack() }, + onNavigateToEvent = { route -> + navController.navigate( + route = route + ) + }, onNavigateToWriteAReply = { nostrEventId -> navController.navigate( route = WriteNewNoteRoute( diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/detail/MetadataEventDetail.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/detail/MetadataEventDetail.kt index a874fbcf..fd56f445 100644 --- a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/detail/MetadataEventDetail.kt +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/composable/widgets/detail/MetadataEventDetail.kt @@ -3,49 +3,53 @@ package ac.aux.compose.ui.composable.widgets.detail import ac.aux.compose.database.model.NostrEvent import ac.aux.compose.database.model.Profile import ac.aux.compose.database.model.intermdiate.LocalNostrEvent -import ac.aux.compose.extensions.toFormattedTimeAndDateString +import ac.aux.compose.repository.NostrRepository +import ac.aux.compose.ui.composable.navigation.routes.NostrEventDetailRoute +import ac.aux.compose.ui.composable.navigation.routes.Route +import ac.aux.compose.ui.composable.widgets.feed.ListView import ac.aux.compose.ui.theme.AuxTheme +import ac.aux.compose.ui.view.model.MetadataEventDetailType +import ac.aux.compose.ui.view.model.MetadataEventDetailViewModel +import ac.aux.compose.ui.view.model.SearchResultType +import ac.aux.compose.ui.view.state.FeedListUIState +import ac.aux.compose.ui.view.state.SearchResultListUIState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.AddReaction import androidx.compose.material.icons.filled.ArrowBackIosNew import androidx.compose.material.icons.filled.Bolt -import androidx.compose.material.icons.filled.ChatBubble -import androidx.compose.material.icons.filled.CurrencyBitcoin import androidx.compose.material.icons.filled.Person -import androidx.compose.material.icons.filled.Repeat -import androidx.compose.material3.BottomAppBar import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LargeTopAppBar +import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.PrimaryScrollableTabRow -import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -55,21 +59,32 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.navigation.compose.ComposeNavigator -import androidx.navigation.compose.DialogNavigator -import androidx.navigation.compose.rememberNavController +import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent -import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import kotlinx.coroutines.launch -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @Composable fun MetadataEventDetail( localNostrEvent: LocalNostrEvent, onNavigateBack: () -> Unit, + onNavigateToEvent: (Route) -> Unit, + nostrRepository: NostrRepository ) { + val scope = rememberCoroutineScope() + val profile = localNostrEvent.profile val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState()) + val metadataEventDetailViewModel: MetadataEventDetailViewModel = viewModel( + factory = MetadataEventDetailViewModel.factory( + initialMetadataEventDetailType = MetadataEventDetailType.Posts, + publicKey = localNostrEvent.nostrEvent.pubKey, + nostrRepository = nostrRepository, + pagerState = rememberPagerState { SearchResultType.entries.size } + ), + ) + Scaffold( modifier = Modifier.fillMaxSize().nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { @@ -173,27 +188,25 @@ fun MetadataEventDetail( } item { - val destinations = arrayOf("Notes", "Replies", "Articles", "Follows", "Following", "Photos", "Videos") - - val startDestination = 0 - var selectedDestination by rememberSaveable { - mutableIntStateOf(startDestination) - } - PrimaryScrollableTabRow( modifier = Modifier.padding(10.dp), edgePadding = 10.dp, - selectedTabIndex = selectedDestination, + selectedTabIndex = metadataEventDetailViewModel.pagerState.currentPage, ) { - destinations.forEachIndexed { index, destination -> + MetadataEventDetailType.entries.forEachIndexed { index, destination -> Tab( - selected = selectedDestination == index, + selected = metadataEventDetailViewModel.pagerState.currentPage == index, onClick = { - selectedDestination = index + + metadataEventDetailViewModel.updateMetadataEventDetailType(destination) { + scope.launch { + metadataEventDetailViewModel.pagerState.animateScrollToPage(destination.ordinal) + } + } }, text = { Text( - text = destination, + text = destination.name, maxLines = 1, overflow = TextOverflow.Ellipsis ) @@ -203,15 +216,99 @@ fun MetadataEventDetail( } Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + modifier = Modifier.fillParentMaxSize() ) { - Spacer( - modifier = Modifier.height(30.dp) - ) - Text("Profile Feed Nav Host Goes here...") + HorizontalPager( + state = metadataEventDetailViewModel.pagerState, + ) { pageIndex -> + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + when (val feedListUIState = metadataEventDetailViewModel.feedListUIState) { + FeedListUIState.Error -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer( + modifier = Modifier.height(50.dp) + ) + Text( + text = "Something went wrong", + ) + } + } + is FeedListUIState.Loaded -> { + if (feedListUIState.localNostrEvents.isEmpty()) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer( + modifier = Modifier.height(50.dp) + ) + Text( + text = "No events were found", + ) + } + } else { + LazyColumn( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + items( + items = feedListUIState.localNostrEvents, + key = { localNostrEvent -> localNostrEvent.nostrEvent.id} + ) { localNostrEvent -> + localNostrEvent.ListView( + onNavigateToEvent = { nostrEventId -> + onNavigateToEvent.invoke( + NostrEventDetailRoute( + nostrEventId = nostrEventId + ) + ) + } + ) + } + } + } + + } + FeedListUIState.Loading -> { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer( + modifier = Modifier.height(50.dp) + ) + Text( + text = "Loading ${MetadataEventDetailType.entries[pageIndex].name.lowercase()}", + textAlign = TextAlign.Center + ) + Spacer( + modifier = Modifier.height(50.dp) + ) + + LoadingIndicator() + } + } + } + } + } } + + LaunchedEffect(true) { + metadataEventDetailViewModel.updateMetadataEventDetailType( + MetadataEventDetailType.Posts + ) { + + } + } + } } } @@ -245,6 +342,8 @@ private fun MetadataEventEventDetailPreview() { ) ), onNavigateBack = {}, + onNavigateToEvent = {}, + nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/MetadataEventDetailViewModel.kt b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/MetadataEventDetailViewModel.kt new file mode 100755 index 00000000..fda057a1 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/aux/compose/ui/view/model/MetadataEventDetailViewModel.kt @@ -0,0 +1,256 @@ +package ac.aux.compose.ui.view.model + +import ac.aux.compose.database.model.SynchronizeNostrEventRequest +import ac.aux.compose.database.model.types.SynchronizationFilter +import ac.aux.compose.nostr.Relays +import ac.aux.compose.repository.NostrRepository +import ac.aux.compose.ui.view.state.FeedListUIState +import ac.aux.compose.ui.view.state.SearchResultListUIState +import androidx.compose.foundation.pager.PagerState +import androidx.compose.foundation.pager.rememberPagerState +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 com.vitorpamplona.quartz.nip02FollowList.ContactListEvent +import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent +import com.vitorpamplona.quartz.nip18Reposts.RepostEvent +import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent +import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent +import com.vitorpamplona.quartz.nip51Lists.bookmarkList.BookmarkListEvent +import com.vitorpamplona.quartz.nip51Lists.relayLists.RelayFeedsListEvent +import com.vitorpamplona.quartz.nip56Reports.ReportEvent +import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + +enum class MetadataEventDetailType { + Posts, Replies, Articles, Followers, Following, Zaps, Photos, Videos, Bookmarks, Reports, Relays +} + +class MetadataEventDetailViewModel( + initialMetadataEventDetailType: MetadataEventDetailType, + val publicKey: HexKey, + val nostrRepository: NostrRepository, + val pagerState: PagerState +): ViewModel() { + val logger = Logger.withTag(TAG) + + + var feedJob: Job? = null + + var metadataEventDetailType: MetadataEventDetailType by mutableStateOf(initialMetadataEventDetailType) + private set + + var feedListUIState: FeedListUIState by mutableStateOf(FeedListUIState.Loading) + private set + + fun updateMetadataEventDetailType (metadataEventDetailType: MetadataEventDetailType, navigateToPage: () -> Unit) { + this.metadataEventDetailType = metadataEventDetailType + + initiateMetadataFeed(this.metadataEventDetailType) + navigateToPage.invoke() + } + + fun initiateMetadataFeed(metadataEventDetailType: MetadataEventDetailType = this.metadataEventDetailType) { + feedListUIState = FeedListUIState.Loading + + logger.d("initiateMetadataFeed") + val synchronizationFilter = when (metadataEventDetailType) { + MetadataEventDetailType.Posts -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + TextNoteEvent.KIND, + RepostEvent.KIND, + ), + limit = 50 + ) + } + MetadataEventDetailType.Replies -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + TextNoteEvent.KIND, + ReactionEvent.KIND, + ), + // TODO: replyTo is set... + limit = 50 + ) + } + MetadataEventDetailType.Articles -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + LongTextNoteEvent.KIND, + ), + limit = 50 + ) + } + MetadataEventDetailType.Followers -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + ContactListEvent.KIND, + ), + // TODO: Check users with hexKey + limit = 50 + ) + } + MetadataEventDetailType.Following -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + ContactListEvent.KIND, + ), + limit = 50 + ) + } + MetadataEventDetailType.Zaps -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + LnZapEvent.KIND, + ), + // TODO: Zaps user has received... + limit = 50 + ) + } + MetadataEventDetailType.Photos -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + TextNoteEvent.KIND, + ), + // TODO: filter for tags of pictures... + limit = 50 + ) + } + MetadataEventDetailType.Videos -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + TextNoteEvent.KIND, + ), + // TODO: filter for tags of videos... + limit = 50 + ) + } + MetadataEventDetailType.Bookmarks -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + BookmarkListEvent.KIND, + ), + limit = 50 + ) + } + MetadataEventDetailType.Reports -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + ReportEvent.KIND, + ), + limit = 50 + ) + } + MetadataEventDetailType.Relays -> { + SynchronizationFilter( + authors = arrayOf( + publicKey + ), + kinds = arrayOf( + RelayFeedsListEvent.KIND, + ), + limit = 50 + ) + } + } + + syncFeed(synchronizationFilter) + loadFeed(synchronizationFilter) + } + + + + private fun syncFeed(synchronizationFilter: SynchronizationFilter) { + logger.d("syncFeed") + + viewModelScope.launch(Dispatchers.IO) { + // Sync Notifications... might want to also run this in the background + nostrRepository.queueSynchronizeNostrEvent( + Relays.eventFinderRelaySet.map { normalizedRelay -> + SynchronizeNostrEventRequest( + purpose = "search", + synchronizationFilters = arrayOf( + synchronizationFilter + ), + relayURL = normalizedRelay.url + ) + } + ) + } + } + + private fun loadFeed(synchronizationFilter: SynchronizationFilter) { + logger.d("loadFeed") + + feedJob?.cancel() + feedJob = viewModelScope.launch(Dispatchers.IO) { + // This is something not taking into consideration the results we are actually looking for... + nostrRepository.observeNostrFeed(synchronizationFilter).collect { localNostrEvents -> + feedListUIState = FeedListUIState.Loaded( + localNostrEvents = localNostrEvents + ) + } + } + } + + companion object { + const val TAG = "SearchViewModel" + + fun factory( + initialMetadataEventDetailType: MetadataEventDetailType = MetadataEventDetailType.Posts, + publicKey: HexKey, + nostrRepository: NostrRepository, + pagerState: PagerState + ): ViewModelProvider.Factory = viewModelFactory { + initializer { + MetadataEventDetailViewModel( + initialMetadataEventDetailType = initialMetadataEventDetailType, + nostrRepository = nostrRepository, + pagerState = pagerState, + publicKey = publicKey + ) + } + } + } +} \ No newline at end of file