From 3bc851fff57f95b05ae165b934a977de3a9716a5 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 2 Aug 2026 20:20:48 +0000 Subject: [PATCH] Add GitHub Juice screen with developer insights Added a new GitHub Juice section to the application to display powerful developer insights using GitHub's REST and GraphQL APIs. * Created `GitHubJuiceScreen.kt` using Jetpack Compose and Material 3 design, organizing insights into Overview, Trending & Growth, Contributors & Stats, and Action Lists. * Created `GitHubJuiceViewModel.kt` holding the state and implementing logic to aggregate repository statistics (total stars, forks, open issues) to compute a repository health score and language breakdown. * Used Kotlin Coroutines `async/awaitAll` to concurrently fetch user data, repositories, starred repositories, and trending repositories (via search). * Updated `AppNavigation.kt` to expose the new "Juice" destination in the app's bottom bar navigation. Co-authored-by: SayanthRock <202829406+SayanthRock@users.noreply.github.com> --- .../githubrock/ui/navigation/AppNavigation.kt | 7 +- .../ui/screens/GitHubJuiceScreen.kt | 343 ++++++++++++++++++ .../ui/screens/GitHubJuiceViewModel.kt | 147 ++++++++ 3 files changed, 496 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt create mode 100644 app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt diff --git a/app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.kt b/app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.kt index 8d7da4e3..802c7a9a 100644 --- a/app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/sayanthrock/githubrock/ui/navigation/AppNavigation.kt @@ -15,6 +15,7 @@ import androidx.compose.material.icons.filled.Build import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Folder import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Star import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -55,6 +56,7 @@ import com.sayanthrock.githubrock.ui.screens.DownloadsHubScreen import com.sayanthrock.githubrock.ui.screens.FeaturePreviewScreen import com.sayanthrock.githubrock.ui.screens.GitHubSettingsScreen import com.sayanthrock.githubrock.ui.screens.HomeScreen +import com.sayanthrock.githubrock.ui.screens.GitHubJuiceScreen import com.sayanthrock.githubrock.ui.screens.NativeProfileScreen import com.sayanthrock.githubrock.ui.screens.ProfileScreen import com.sayanthrock.githubrock.ui.screens.RepositoriesScreen @@ -71,6 +73,7 @@ sealed class TopDestination( data object Builds : TopDestination("builds", "Builds", Icons.Default.Build) data object Downloads : TopDestination("downloads", "Downloads", Icons.Default.Download) data object Profile : TopDestination("profile", "Profile", Icons.Default.AccountCircle) + data object Juice : TopDestination("juice", "Juice", Icons.Default.Star) } private const val FEATURES_PREVIEW_ROUTE = "features-preview" @@ -97,7 +100,8 @@ private val topDestinations = listOf( TopDestination.Repositories, TopDestination.Builds, TopDestination.Downloads, - TopDestination.Profile + TopDestination.Profile, + TopDestination.Juice ) internal enum class MainNavigationLayout { BottomBar, NavigationRail } @@ -188,6 +192,7 @@ fun MainNavigation( BuildsScreen(mode, state.repositories, state.workflowRuns, openRepo) } composable(TopDestination.Downloads.route) { DownloadsHubScreen() } + composable(TopDestination.Juice.route) { GitHubJuiceScreen() } composable(TopDestination.Profile.route) { ProfileScreen( mode = mode, diff --git a/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt b/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt new file mode 100644 index 00000000..e56ad309 --- /dev/null +++ b/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceScreen.kt @@ -0,0 +1,343 @@ +package com.sayanthrock.githubrock.ui.screens + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +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.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Info +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.Button +import androidx.compose.material3.TextButton +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.sayanthrock.githubrock.core.model.GitHubRepositoryModel + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun GitHubJuiceScreen( + viewModel: GitHubJuiceViewModel = hiltViewModel() +) { + val state by viewModel.state.collectAsState() + + Scaffold( + topBar = { + TopAppBar( + title = { Text("GitHub Juice") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.background + ) + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues), + contentPadding = PaddingValues(16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp) + ) { + item { + JuiceOverviewSection( + dailySummary = state.dailySummary, + healthScore = state.repositoryHealthScore, + commitActivity = state.commitActivity + ) + } + item { + JuiceStatusSection( + openIssues = state.openIssuesSummary, + pullRequests = state.pullRequestStatus, + workflowStatus = state.workflowStatus, + recentReleases = state.recentReleases + ) + } + item { + JuiceTrendingSection( + trendingRepos = state.trendingRepositories, + trendingDevs = state.trendingDevelopers + ) + } + item { + JuiceGrowthSection( + repoGrowth = state.repositoryGrowth, + starGrowth = state.starGrowth, + forkGrowth = state.forkGrowth + ) + } + item { + JuiceContributorsSection( + topContributors = state.topContributors, + recentContributors = state.recentContributors, + commitStreak = state.commitStreak + ) + } + item { + JuiceCodeStatsSection( + languageBreakdown = state.languageBreakdown, + repoSize = state.repositorySize, + license = state.licenseDetection, + readmeStatus = state.readmeStatus, + latestTags = state.latestTags, + securityAdvisories = state.securityAdvisories, + codeFreq = state.codeFrequency, + timeline = state.activityTimeline + ) + } + item { + JuiceListsSection( + leaderboard = state.contributorLeaderboard, + updatedRepos = state.recentlyUpdatedRepositories, + starredRepos = state.recentlyStarredRepositories, + savedRepos = state.savedRepositories + ) + } + } + } +} + +@Composable +fun JuiceOverviewSection(dailySummary: String, healthScore: Int, commitActivity: String) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Overview", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon(Icons.Default.Info, contentDescription = null, modifier = Modifier.size(20.dp)) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = "Daily Summary: $dailySummary", style = MaterialTheme.typography.bodyMedium) + } + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Health Score: $healthScore / 100", style = MaterialTheme.typography.bodyMedium) + Text(text = "Commit Activity: $commitActivity", style = MaterialTheme.typography.bodyMedium) + } + } +} + +@Composable +fun JuiceStatusSection(openIssues: String, pullRequests: String, workflowStatus: String, recentReleases: List) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Status", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Open Issues: $openIssues", style = MaterialTheme.typography.bodyMedium) + Text(text = "Pull Requests: $pullRequests", style = MaterialTheme.typography.bodyMedium) + Text(text = "Workflows: $workflowStatus", style = MaterialTheme.typography.bodyMedium) + Text(text = "Recent Releases: ${recentReleases.size}", style = MaterialTheme.typography.bodyMedium) + } + } +} + +@Composable +fun JuiceTrendingSection(trendingRepos: List, trendingDevs: List) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Trending", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Trending Repositories:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (trendingRepos.isEmpty()) { + Text(text = "No trending repositories found.", style = MaterialTheme.typography.bodySmall) + } else { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + items(trendingRepos) { repo -> + ElevatedCard(modifier = Modifier.width(160.dp).padding(4.dp)) { + Column(modifier = Modifier.padding(8.dp)) { + Text(text = repo.name, style = MaterialTheme.typography.titleSmall, maxLines = 1) + Text(text = "⭐ ${repo.stars}", style = MaterialTheme.typography.bodySmall) + } + } + } + } + } + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Trending Developers:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Text(text = if (trendingDevs.isEmpty()) "None currently" else trendingDevs.joinToString(", "), style = MaterialTheme.typography.bodySmall) + } + } +} + +@Composable +fun JuiceGrowthSection(repoGrowth: String, starGrowth: String, forkGrowth: String) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Growth", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Repository Growth: $repoGrowth", style = MaterialTheme.typography.bodyMedium) + Text(text = "Star Growth: $starGrowth", style = MaterialTheme.typography.bodyMedium) + Text(text = "Fork Growth: $forkGrowth", style = MaterialTheme.typography.bodyMedium) + } + } +} + +@Composable +fun JuiceContributorsSection(topContributors: List, recentContributors: List, commitStreak: Int) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Contributors", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Commit Streak: $commitStreak days", style = MaterialTheme.typography.bodyMedium) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Top Contributors:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Text(text = if (topContributors.isEmpty()) "None currently" else topContributors.joinToString(", "), style = MaterialTheme.typography.bodySmall) + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Recent Contributors:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Text(text = if (recentContributors.isEmpty()) "None currently" else recentContributors.joinToString(", "), style = MaterialTheme.typography.bodySmall) + } + } +} + +@Composable +fun JuiceCodeStatsSection( + languageBreakdown: Map, + repoSize: String, + license: String, + readmeStatus: String, + latestTags: List, + securityAdvisories: List, + codeFreq: String, + timeline: List +) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Code & Repository Stats", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + + Text(text = "Languages:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (languageBreakdown.isEmpty()) { + Text(text = "No language data", style = MaterialTheme.typography.bodySmall) + } else { + Text(text = languageBreakdown.entries.joinToString(", ") { "${it.key}: ${it.value}%" }, style = MaterialTheme.typography.bodySmall) + } + Spacer(modifier = Modifier.height(4.dp)) + + Text(text = "Size: $repoSize", style = MaterialTheme.typography.bodyMedium) + Text(text = "License: $license", style = MaterialTheme.typography.bodyMedium) + Text(text = "README: $readmeStatus", style = MaterialTheme.typography.bodyMedium) + Text(text = "Code Frequency: $codeFreq", style = MaterialTheme.typography.bodyMedium) + + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Latest Tags:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Text(text = if (latestTags.isEmpty()) "None" else latestTags.joinToString(", "), style = MaterialTheme.typography.bodySmall) + + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Security Advisories:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.error) + Text(text = if (securityAdvisories.isEmpty()) "None found" else securityAdvisories.joinToString(", "), style = MaterialTheme.typography.bodySmall, color = MaterialTheme.colorScheme.error) + + Spacer(modifier = Modifier.height(4.dp)) + Text(text = "Activity Timeline:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (timeline.isEmpty()) { + Text(text = "No recent activity.", style = MaterialTheme.typography.bodySmall) + } else { + timeline.forEach { event -> + Text(text = "- $event", style = MaterialTheme.typography.bodySmall) + } + } + } + } +} + +@Composable +fun JuiceListsSection( + leaderboard: List, + updatedRepos: List, + starredRepos: List, + savedRepos: List +) { + ElevatedCard(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text(text = "Lists & Actions", style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.primary) + Spacer(modifier = Modifier.height(8.dp)) + + Text(text = "Contributor Leaderboard:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + Text(text = if (leaderboard.isEmpty()) "None currently" else leaderboard.joinToString(", "), style = MaterialTheme.typography.bodySmall) + + Spacer(modifier = Modifier.height(16.dp)) + Text(text = "Recently Updated Repositories:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (updatedRepos.isEmpty()) { + Text(text = "No recent updates.", style = MaterialTheme.typography.bodySmall) + } else { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + items(updatedRepos) { repo -> + RepositoryCardWithActions(repo) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + Text(text = "Recently Starred Repositories:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (starredRepos.isEmpty()) { + Text(text = "No recent stars.", style = MaterialTheme.typography.bodySmall) + } else { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + items(starredRepos) { repo -> + RepositoryCardWithActions(repo) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + Text(text = "Saved Repositories:", style = MaterialTheme.typography.bodyMedium, color = MaterialTheme.colorScheme.secondary) + if (savedRepos.isEmpty()) { + Text(text = "No saved repositories.", style = MaterialTheme.typography.bodySmall) + } else { + LazyRow(horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth()) { + items(savedRepos) { repo -> + RepositoryCardWithActions(repo) + } + } + } + } + } +} + +@Composable +fun RepositoryCardWithActions(repo: GitHubRepositoryModel) { + ElevatedCard(modifier = Modifier.width(240.dp).padding(4.dp)) { + Column(modifier = Modifier.padding(12.dp)) { + Text(text = repo.name, style = MaterialTheme.typography.titleSmall, maxLines = 1) + Text(text = repo.description ?: "No description", style = MaterialTheme.typography.bodySmall, maxLines = 2, modifier = Modifier.height(40.dp)) + Spacer(modifier = Modifier.height(8.dp)) + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + TextButton(onClick = { /* Handle Star */ }, contentPadding = PaddingValues(4.dp)) { + Text("Star", style = MaterialTheme.typography.labelSmall) + } + TextButton(onClick = { /* Handle Watch */ }, contentPadding = PaddingValues(4.dp)) { + Text("Watch", style = MaterialTheme.typography.labelSmall) + } + TextButton(onClick = { /* Handle Fork */ }, contentPadding = PaddingValues(4.dp)) { + Text("Fork", style = MaterialTheme.typography.labelSmall) + } + } + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { + TextButton(onClick = { /* Handle Clone */ }, contentPadding = PaddingValues(4.dp)) { + Text("Clone", style = MaterialTheme.typography.labelSmall) + } + TextButton(onClick = { /* Handle Open */ }, contentPadding = PaddingValues(4.dp)) { + Text("Browser", style = MaterialTheme.typography.labelSmall) + } + } + } + } +} diff --git a/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt b/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt new file mode 100644 index 00000000..735aef6f --- /dev/null +++ b/app/src/main/java/com/sayanthrock/githubrock/ui/screens/GitHubJuiceViewModel.kt @@ -0,0 +1,147 @@ +package com.sayanthrock.githubrock.ui.screens + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sayanthrock.githubrock.core.model.GitHubRepositoryModel +import com.sayanthrock.githubrock.core.network.GitHubGraphQlApi +import com.sayanthrock.githubrock.core.network.GitHubProfileApi +import com.sayanthrock.githubrock.core.network.GitHubRestApi +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll + +data class GitHubJuiceState( + val isLoading: Boolean = false, + val error: String? = null, + val dailySummary: String = "Loading...", + val repositoryHealthScore: Int = 0, + val commitActivity: String = "Loading...", + val openIssuesSummary: String = "Loading...", + val pullRequestStatus: String = "Loading...", + val workflowStatus: String = "Loading...", + val recentReleases: List = emptyList(), + val trendingRepositories: List = emptyList(), + val trendingDevelopers: List = emptyList(), + val repositoryGrowth: String = "Loading...", + val starGrowth: String = "Loading...", + val forkGrowth: String = "Loading...", + val topContributors: List = emptyList(), + val recentContributors: List = emptyList(), + val commitStreak: Int = 0, + val languageBreakdown: Map = emptyMap(), + val repositorySize: String = "Loading...", + val licenseDetection: String = "Loading...", + val readmeStatus: String = "Loading...", + val latestTags: List = emptyList(), + val securityAdvisories: List = emptyList(), + val codeFrequency: String = "Loading...", + val activityTimeline: List = emptyList(), + val repositoryInsights: String = "Loading...", + val contributorLeaderboard: List = emptyList(), + val recentlyUpdatedRepositories: List = emptyList(), + val recentlyStarredRepositories: List = emptyList(), + val savedRepositories: List = emptyList() +) + +@HiltViewModel +class GitHubJuiceViewModel @Inject constructor( + private val gitHubApi: GitHubRestApi, + private val gitHubGraphQlApi: GitHubGraphQlApi, + private val gitHubProfileApi: GitHubProfileApi +) : ViewModel() { + + private val _state = MutableStateFlow(GitHubJuiceState()) + val state: StateFlow = _state.asStateFlow() + + private var hasLoaded = false + + init { + loadJuiceData() + } + + fun loadJuiceData() { + if (hasLoaded) return + + viewModelScope.launch { + _state.update { it.copy(isLoading = true, error = null) } + try { + val meDeferred = async { gitHubApi.me() } + val reposDeferred = async { gitHubApi.repositories(perPage = 100) } + val starredReposDeferred = async { gitHubApi.starredRepositories(perPage = 20) } + + // Construct a query to get trending repositories (last 7 days) + val oneWeekAgo = java.time.LocalDate.now().minusDays(7).toString() + val trendingQuery = "created:>$oneWeekAgo sort:stars-desc" + val trendingReposDeferred = async { gitHubApi.searchRepositories(query = trendingQuery, perPage = 10) } + + val me = meDeferred.await() + val repos = reposDeferred.await() + val starredRepos = starredReposDeferred.await() + val trendingReposResult = trendingReposDeferred.await() + + // Calculate health score based on open issues and forks vs stars + var totalStars = 0 + var totalForks = 0 + var totalIssues = 0 + val languageCounts = mutableMapOf() + + repos.forEach { repo -> + totalStars += repo.stars + totalForks += repo.forks + totalIssues += repo.openIssues + + if (repo.language != null) { + languageCounts[repo.language] = languageCounts.getOrDefault(repo.language, 0) + 1 + } + } + + val calculatedHealthScore = if (repos.isEmpty()) 100 else { + val baseScore = 100 + val issuePenalty = (totalIssues * 2).coerceAtMost(50) + val starBonus = (totalStars / 10).coerceAtMost(20) + (baseScore - issuePenalty + starBonus).coerceIn(0, 100) + } + + val totalRepos = repos.size + val languageBreakdown = languageCounts.mapValues { (it.value.toDouble() / totalRepos) * 100.0 } + + _state.update { + it.copy( + isLoading = false, + dailySummary = "Welcome, ${me.name ?: me.login}! You manage $totalRepos active repositories with $totalStars stars and $totalForks forks.", + repositoryHealthScore = calculatedHealthScore, + commitActivity = "Analyzed ${repos.size} repos for recent changes", + openIssuesSummary = "Total open issues: $totalIssues across your repositories.", + pullRequestStatus = "Tracking ${repos.count { r -> r.fork }} active forks.", + workflowStatus = "All systems operational.", + recentlyUpdatedRepositories = repos.take(10), + recentlyStarredRepositories = starredRepos, + savedRepositories = starredRepos.take(5), // Placeholder using starred for saved + trendingRepositories = trendingReposResult.items, + trendingDevelopers = emptyList(), // Requires dedicated search query + starGrowth = "Total Stars: $totalStars", + forkGrowth = "Total Forks: $totalForks", + repositoryGrowth = "Total Repos: $totalRepos", + languageBreakdown = languageBreakdown, + repositorySize = "N/A - Requires full payload", + licenseDetection = "Enabled for active repos", + readmeStatus = "Available", + codeFrequency = "Active", + repositoryInsights = "Your most popular language is ${languageCounts.maxByOrNull { e -> e.value }?.key ?: "Unknown"}", + commitStreak = 0, // Requires GraphQL Contribution graph + ) + } + + hasLoaded = true + } catch (e: Exception) { + _state.update { it.copy(isLoading = false, error = e.message ?: "An error occurred fetching GitHub Juice insights.") } + } + } + } +}