diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt index fb5b0d986e..6d640d3c89 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/Git.kt @@ -52,12 +52,15 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.EmptyTagList import com.vitorpamplona.amethyst.commons.model.toImmutableListOfLists +import com.vitorpamplona.amethyst.commons.nip34Git.GitBrowseState +import com.vitorpamplona.amethyst.commons.nip34Git.GitRepositoryBrowserViewModel import com.vitorpamplona.amethyst.model.AddressableNote import com.vitorpamplona.amethyst.model.GitPullRequestUpdateIndex import com.vitorpamplona.amethyst.model.Note @@ -71,6 +74,11 @@ import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.GitRepositoryBrowserViewModelFactory +import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.RepoLanguageBar +import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.RepoLastCommit +import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.RepoStatTiles +import com.vitorpamplona.amethyst.ui.screen.loggedIn.gitRepo.computeLanguageBreakdown import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.Font12SP import com.vitorpamplona.amethyst.ui.theme.HalfDoubleVertSpacer @@ -785,8 +793,6 @@ private fun RenderGitRepositoryEvent( ) { val title = noteEvent.name() ?: noteEvent.dTag() val summary = noteEvent.description() - val web = noteEvent.web() - val clone = noteEvent.clone() val topics = remember(noteEvent) { noteEvent.hashtags().filter { it.isNotBlank() } } val isPersonalFork = remember(noteEvent) { noteEvent.isPersonalFork() } @@ -840,25 +846,7 @@ private fun RenderGitRepositoryEvent( ) } - if (web != null || clone != null) { - Spacer(modifier = HalfDoubleVertSpacer) - Column(verticalArrangement = Arrangement.spacedBy(Size5dp)) { - web?.let { - LinkRow( - symbol = MaterialSymbols.Public, - contentDescription = stringRes(id = R.string.git_web_address), - url = it, - ) - } - clone?.let { - LinkRow( - symbol = MaterialSymbols.AutoMirrored.OpenInNew, - contentDescription = stringRes(id = R.string.git_clone_address), - url = it, - ) - } - } - } + RepoSnapshotDashboard(noteEvent, note, accountViewModel) if (topics.isNotEmpty()) { Spacer(modifier = HalfDoubleVertSpacer) @@ -877,3 +865,43 @@ private fun RenderGitRepositoryEvent( } } } + +/** + * Loads a shallow snapshot of the repository over smart-HTTP and renders the same + * stat tiles / language bar / last-commit strip used on the project home, in place of + * the old web/clone links. Fetches lazily (once) when the card is composed. + */ +@Composable +private fun RepoSnapshotDashboard( + noteEvent: GitRepositoryEvent, + note: Note, + accountViewModel: AccountViewModel, +) { + val browser: GitRepositoryBrowserViewModel = + viewModel( + key = note.idHex + "GitRepoCardBrowser", + factory = GitRepositoryBrowserViewModelFactory(accountViewModel.httpClientBuilder::okHttpClientForPreview), + ) + LaunchedEffect(noteEvent) { browser.loadOnce(noteEvent.clones()) } + val browserState by browser.state.collectAsStateWithLifecycle() + val snapshot = (browserState as? GitBrowseState.Loaded)?.snapshot ?: return + + val fileNames = remember(snapshot) { snapshot.walkFileNames() } + val slices = remember(fileNames) { computeLanguageBreakdown(fileNames) } + + Spacer(modifier = HalfDoubleVertSpacer) + RepoStatTiles( + branches = snapshot.branches.size, + tags = snapshot.tags.size, + files = fileNames.size, + updatedEpochSec = snapshot.tipCommit?.authorTimeSec, + ) + if (slices.isNotEmpty()) { + Spacer(modifier = HalfDoubleVertSpacer) + RepoLanguageBar(slices) + } + snapshot.tipCommit?.let { + Spacer(modifier = HalfDoubleVertSpacer) + RepoLastCommit(it) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryScreen.kt index d047a14719..52d6cf36be 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/gitRepo/GitRepositoryScreen.kt @@ -72,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.nip34Git.GitRepositoryBrowserViewModel import com.vitorpamplona.amethyst.commons.ui.feeds.FeedState import com.vitorpamplona.amethyst.commons.ui.layouts.LocalDisappearingScaffoldPadding import com.vitorpamplona.amethyst.model.AddressableNote +import com.vitorpamplona.amethyst.model.GitStatusIndex import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel @@ -82,6 +83,7 @@ import com.vitorpamplona.amethyst.ui.navigation.topbars.ShorterTopAppBar import com.vitorpamplona.amethyst.ui.navigation.topbars.TitleIconModifier import com.vitorpamplona.amethyst.ui.note.ArrowBackIcon import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote +import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.screen.FeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -163,7 +165,7 @@ fun GitRepositoryPullsScreen( * Builds the [GitRepositoryBrowserViewModel]. The factory lives app-side because the KMP * lifecycle artifact used in commons doesn't expose the `create(Class)` override. */ -private class GitRepositoryBrowserViewModelFactory( +internal class GitRepositoryBrowserViewModelFactory( private val okHttpClient: (String) -> OkHttpClient, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T = GitRepositoryBrowserViewModel(okHttpClient) as T @@ -258,6 +260,20 @@ private fun GitRepositoryHome( .take(6) } + // Nav-card badges count only the OPEN issues/PRs. The open/closed split needs the status + // index (kinds 1630-1633), which is started here so the home reflects it without visiting + // the Issues screen first; the count is then derived directly from the live index. + LaunchedEffect(Unit) { GitStatusIndex.startIfNeeded() } + val statusMap by GitStatusIndex.latestByTarget.collectAsStateWithLifecycle() + val openIssueCount = + remember(openIssueItems, closedIssueItems, statusMap) { + (openIssueItems + closedIssueItems).distinctBy { it.idHex }.count { !GitStatusIndex.isClosedOrResolved(it.idHex, statusMap) } + } + val openPullCount = + remember(openPatchItems, closedPatchItems, statusMap) { + (openPatchItems + closedPatchItems).distinctBy { it.idHex }.count { !GitStatusIndex.isClosedOrResolved(it.idHex, statusMap) } + } + var showSettings by rememberSaveable(note.idHex) { mutableStateOf(false) } val currentEventForSettings = event if (showSettings && currentEventForSettings != null) { @@ -290,6 +306,9 @@ private fun GitRepositoryHome( Icon(MaterialSymbols.Edit, contentDescription = stringRes(R.string.git_repo_settings_title)) } } + Row(Modifier.padding(end = 6.dp), verticalAlignment = Alignment.CenterVertically) { + MoreOptionsButton(note, accountViewModel = accountViewModel, nav = nav) + } }, ) }, @@ -309,7 +328,6 @@ private fun GitRepositoryHome( if (currentEvent != null) { RepoHero(currentEvent) RepoMaintainersRow(currentEvent, accountViewModel, nav) - RepoSocialRow(note, accountViewModel, nav) } if (snapshot != null) { @@ -325,11 +343,12 @@ private fun GitRepositoryHome( snapshot.tipCommit?.let { RepoLastCommit(it) } } - RepoNavCards(note, openIssueItems.size, openPatchItems.size, nav) + RepoNavCards(note, openIssueCount, openPullCount, nav) RepoActivityPulse(activity, accountViewModel, nav) if (currentEvent != null) { + RepoSocialRow(note, accountViewModel, nav) GitReadmeSection(browserState, browserViewModel, currentEvent, accountViewModel, nav) } else { EmptyMessage(stringRes(R.string.loading_feed)) @@ -462,7 +481,7 @@ private fun RepoNavCards( ) { Column( modifier = Modifier.fillMaxWidth(), - verticalArrangement = Arrangement.spacedBy(8.dp), + verticalArrangement = Arrangement.spacedBy(6.dp), ) { RepoNavCard(MaterialSymbols.Code, stringRes(R.string.git_repo_tab_code), null) { nav.nav(Route.GitRepositoryCode(note.address)) @@ -490,7 +509,7 @@ private fun RepoNavCard( .clip(RoundedCornerShape(15.dp)) .background(MaterialTheme.colorScheme.surface) .clickable(onClick = onClick) - .padding(horizontal = 16.dp, vertical = 16.dp), + .padding(horizontal = 16.dp, vertical = 12.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(14.dp), ) {