Skip to content

Commit ba2f0ab

Browse files
authored
Merge pull request #13 from Coding-Meet/feature/analytics
Refactor UI components and enhance documentation and marketing copy
2 parents 632d14a + 373707f commit ba2f0ab

51 files changed

Lines changed: 645 additions & 140 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ Privacy is a core design principle of DevAnalyzer:
8585
* **Local Processing**: All analysis runs locally. Your code, project names, and file paths are never uploaded.
8686
* **Optional Analytics**: You can opt in or out of anonymous analytics at any time.
8787
* **Zero PII**: No email addresses, personal details, or sensitive metadata are tracked.
88-
* **SDK Configuration**: We use `PersonProfiles.IDENTIFIED_ONLY` internally in the PostHog SDK configuration. Since we never call `PostHog.identify`, all users remain completely anonymous.
88+
* **SDK Configuration**: We configure the analytics client to keep all metrics completely anonymous. Since we never call identification methods, all users remain completely anonymous.
8989

9090
---
9191

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/main.kt‎

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,24 @@ fun main() {
139139
isDarkMode = appUiState.isDarkMode,
140140
onThemeChange = {
141141
appViewModel.handleIntent(AppUiIntent.ChangeTheme(appUiState.isDarkMode))
142+
},
143+
updateDialogWithNavigation = { currentNavigationItem ->
144+
val updateState = appViewModel.updateDialogState
145+
if (updateState is UpdateDialogState.Available && currentNavigationItem != null) {
146+
LaunchedEffect(updateState) {
147+
appViewModel.trackUpdateDialogShown()
148+
}
149+
UpdateDialog(
150+
state = updateState,
151+
onDismiss = {
152+
appViewModel.trackUpdateDismissed()
153+
appViewModel.closeUpdateDialog()
154+
},
155+
onUpdateClicked = {
156+
appViewModel.trackUpdateClicked()
157+
}
158+
)
159+
}
142160
}
143161
)
144162

@@ -157,22 +175,6 @@ fun main() {
157175
)
158176
}
159177
}
160-
val updateState = appViewModel.updateDialogState
161-
if (updateState is UpdateDialogState.Available) {
162-
LaunchedEffect(updateState) {
163-
appViewModel.trackUpdateDialogShown()
164-
}
165-
UpdateDialog(
166-
state = updateState,
167-
onDismiss = {
168-
appViewModel.trackUpdateDismissed()
169-
appViewModel.closeUpdateDialog()
170-
},
171-
onUpdateClicked = {
172-
appViewModel.trackUpdateClicked()
173-
}
174-
)
175-
}
176178
}
177179
}
178180
}

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/components/EmptyStateCardLayout.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ fun EmptyStateCardLayout(
8686
)
8787
) {
8888
Icon(
89-
Icons.Default.Search,
89+
icon,
9090
contentDescription = null,
9191
modifier = Modifier.size(18.dp)
9292
)

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/navigation/AppNavigation.kt‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import com.meet.dev.analyzer.presentation.screen.workspace.WorkspaceScreen
2828
fun AppNavigation(
2929
isDarkMode: Boolean,
3030
onThemeChange: () -> Unit,
31+
updateDialogWithNavigation: @Composable (NavigationItem?) -> Unit
3132
) {
3233
val navController = rememberNavController()
3334
val navBackStackEntry by navController.currentBackStackEntryAsState()
@@ -137,4 +138,5 @@ fun AppNavigation(
137138
}
138139
}
139140
}
141+
updateDialogWithNavigation(currentNavigationItem)
140142
}

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/cleanbuild/CleanBuildScreen.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,10 @@ fun CleanBuildScreenContent(
264264
}
265265
} else {
266266
EmptyStateCardLayout(
267-
title = "No Projects Selected",
268-
description = "Browse and select Android Studio projects to clean",
267+
title = "Select a Project Directory",
268+
description = "Choose a folder containing your Android Studio projects to scan for build folders and safely remove them to reclaim disk space.",
269+
actionText = "Select Folder",
270+
onAction = onBrowseClick,
269271
icon = Icons.Default.FolderOpen,
270272
modifier = Modifier.padding(10.dp).fillMaxSize()
271273
)

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/project/ProjectAnalyzerScreen.kt‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,8 +220,10 @@ fun ProjectAnalyzerContent(
220220
}
221221
} else {
222222
EmptyStateCardLayout(
223-
title = "No Project Selected",
224-
description = "Browse and select an Android Studio project folder, then click Analyze to view modules, plugins, and dependencies.",
223+
title = "Select an Android Project",
224+
description = "Choose an Android Studio project to analyze its modules, Gradle configuration, plugins, dependencies, SDK versions, and build files.",
225+
actionText = "Select Project",
226+
onAction = onBrowseClick,
225227
icon = Icons.Default.FolderOpen,
226228
modifier = Modifier.padding(10.dp).fillMaxSize()
227229
)

‎composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/project/components/ModulesTabContent.kt‎

Lines changed: 183 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@ import androidx.compose.foundation.shape.CircleShape
2424
import androidx.compose.foundation.shape.RoundedCornerShape
2525
import androidx.compose.material.icons.Icons
2626
import androidx.compose.material.icons.filled.AccountTree
27+
import androidx.compose.material.icons.filled.DataObject
2728
import androidx.compose.material.icons.filled.ExpandLess
2829
import androidx.compose.material.icons.filled.ExpandMore
2930
import androidx.compose.material.icons.filled.Extension
31+
import androidx.compose.material.icons.filled.Folder
3032
import androidx.compose.material.icons.filled.Plumbing
3133
import androidx.compose.material3.CardDefaults
3234
import androidx.compose.material3.HorizontalDivider
@@ -47,13 +49,15 @@ import androidx.compose.ui.graphics.vector.ImageVector
4749
import androidx.compose.ui.input.pointer.PointerIcon
4850
import androidx.compose.ui.input.pointer.pointerHoverIcon
4951
import androidx.compose.ui.text.font.FontWeight
52+
import androidx.compose.ui.text.style.TextDecoration
5053
import androidx.compose.ui.text.style.TextOverflow
5154
import androidx.compose.ui.unit.dp
5255
import com.meet.dev.analyzer.data.models.project.Dependency
5356
import com.meet.dev.analyzer.data.models.project.ModuleBuildFileInfo
5457
import com.meet.dev.analyzer.data.models.project.Plugin
5558
import com.meet.dev.analyzer.presentation.components.EmptyStateCardLayout
5659
import com.meet.dev.analyzer.presentation.components.VerticalScrollBarLayout
60+
import com.meet.dev.analyzer.utility.platform.FolderFileUtils
5761
import java.awt.Cursor
5862

5963

@@ -66,12 +70,18 @@ fun ModulesTabContent(
6670
val scrollState = rememberLazyGridState()
6771
LazyVerticalGrid(
6872
state = scrollState,
69-
columns = GridCells.Adaptive(minSize = 400.dp),
73+
columns = GridCells.Adaptive(minSize = 360.dp),
7074
modifier = Modifier.fillMaxSize(),
7175
contentPadding = PaddingValues(16.dp),
72-
verticalArrangement = Arrangement.spacedBy(16.dp),
73-
horizontalArrangement = Arrangement.spacedBy(16.dp)
76+
verticalArrangement = Arrangement.spacedBy(12.dp),
77+
horizontalArrangement = Arrangement.spacedBy(12.dp)
7478
) {
79+
if (moduleBuildFileInfos.isNotEmpty()) {
80+
item(span = { GridItemSpan(maxLineSpan) }) {
81+
ModulesSummaryCard(moduleBuildFileInfos = moduleBuildFileInfos)
82+
}
83+
}
84+
7585
items(
7686
items = moduleBuildFileInfos,
7787
key = { module -> module.uniqueId }
@@ -82,7 +92,9 @@ fun ModulesTabContent(
8292
plugins = module.plugins,
8393
fileName = module.type.fileName,
8494
dependencies = module.dependencies,
85-
projectName = projectName
95+
projectName = projectName,
96+
modulePath = module.modulePath,
97+
isRootBuildFile = module.isRootBuildFile
8698
)
8799
}
88100

@@ -100,6 +112,126 @@ fun ModulesTabContent(
100112
}
101113
}
102114

115+
@Composable
116+
private fun ModulesSummaryCard(moduleBuildFileInfos: List<ModuleBuildFileInfo>) {
117+
val totalSize = FolderFileUtils.formatSize(moduleBuildFileInfos.sumOf { it.sizeBytes })
118+
val pluginCount = moduleBuildFileInfos.sumOf { it.plugins.size }
119+
val dependencyCount = moduleBuildFileInfos.sumOf { it.dependencies.size }
120+
val configurationCount = moduleBuildFileInfos
121+
.flatMap { module -> module.plugins.map { it.configuration } + module.dependencies.map { it.configuration } }
122+
.distinct()
123+
.size
124+
125+
OutlinedCard(
126+
modifier = Modifier.fillMaxWidth(),
127+
colors = CardDefaults.outlinedCardColors(
128+
containerColor = MaterialTheme.colorScheme.surface
129+
)
130+
) {
131+
Column(
132+
modifier = Modifier.padding(16.dp),
133+
verticalArrangement = Arrangement.spacedBy(12.dp)
134+
) {
135+
Row(
136+
modifier = Modifier.fillMaxWidth(),
137+
verticalAlignment = Alignment.CenterVertically,
138+
horizontalArrangement = Arrangement.spacedBy(10.dp)
139+
) {
140+
Icon(
141+
Icons.Default.AccountTree,
142+
contentDescription = null,
143+
tint = MaterialTheme.colorScheme.primary,
144+
modifier = Modifier.size(24.dp)
145+
)
146+
Text(
147+
"Modules Overview",
148+
style = MaterialTheme.typography.titleLarge,
149+
fontWeight = FontWeight.Bold
150+
)
151+
}
152+
153+
Row(
154+
modifier = Modifier.fillMaxWidth(),
155+
horizontalArrangement = Arrangement.spacedBy(10.dp)
156+
) {
157+
ModuleSummaryTile(
158+
label = "Modules",
159+
value = moduleBuildFileInfos.size.toString(),
160+
icon = Icons.Default.AccountTree,
161+
modifier = Modifier.weight(1f)
162+
)
163+
ModuleSummaryTile(
164+
label = "Total Size",
165+
value = totalSize,
166+
icon = Icons.Default.Folder,
167+
modifier = Modifier.weight(1f)
168+
)
169+
ModuleSummaryTile(
170+
label = "Plugins",
171+
value = pluginCount.toString(),
172+
icon = Icons.Default.Plumbing,
173+
modifier = Modifier.weight(1f)
174+
)
175+
ModuleSummaryTile(
176+
label = "Dependencies",
177+
value = dependencyCount.toString(),
178+
icon = Icons.Default.DataObject,
179+
modifier = Modifier.weight(1f)
180+
)
181+
ModuleSummaryTile(
182+
label = "Configurations",
183+
value = configurationCount.toString(),
184+
icon = Icons.Default.Extension,
185+
modifier = Modifier.weight(1f)
186+
)
187+
}
188+
}
189+
}
190+
}
191+
192+
@Composable
193+
private fun ModuleSummaryTile(
194+
label: String,
195+
value: String,
196+
icon: ImageVector,
197+
modifier: Modifier = Modifier
198+
) {
199+
Surface(
200+
modifier = modifier,
201+
color = MaterialTheme.colorScheme.surfaceVariant,
202+
shape = MaterialTheme.shapes.medium
203+
) {
204+
Row(
205+
modifier = Modifier.padding(horizontal = 12.dp, vertical = 10.dp),
206+
verticalAlignment = Alignment.CenterVertically,
207+
horizontalArrangement = Arrangement.spacedBy(8.dp)
208+
) {
209+
Icon(
210+
icon,
211+
contentDescription = null,
212+
tint = MaterialTheme.colorScheme.primary,
213+
modifier = Modifier.size(18.dp)
214+
)
215+
Column {
216+
Text(
217+
value,
218+
style = MaterialTheme.typography.titleMedium,
219+
fontWeight = FontWeight.Bold,
220+
maxLines = 1,
221+
overflow = TextOverflow.Ellipsis
222+
)
223+
Text(
224+
label,
225+
style = MaterialTheme.typography.labelSmall,
226+
color = MaterialTheme.colorScheme.onSurfaceVariant,
227+
maxLines = 1,
228+
overflow = TextOverflow.Ellipsis
229+
)
230+
}
231+
}
232+
}
233+
}
234+
103235

104236
@Composable
105237
fun DetailedModuleCard(
@@ -109,6 +241,8 @@ fun DetailedModuleCard(
109241
plugins: List<Plugin>,
110242
dependencies: List<Dependency>,
111243
projectName: String,
244+
modulePath: String,
245+
isRootBuildFile: Boolean,
112246
) {
113247

114248
OutlinedCard(
@@ -119,9 +253,8 @@ fun DetailedModuleCard(
119253
) {
120254
Column(
121255
modifier = Modifier.padding(20.dp),
122-
verticalArrangement = Arrangement.spacedBy(12.dp)
256+
verticalArrangement = Arrangement.spacedBy(10.dp)
123257
) {
124-
// Module header
125258
Row(
126259
modifier = Modifier.fillMaxWidth(),
127260
verticalAlignment = Alignment.CenterVertically
@@ -131,7 +264,14 @@ fun DetailedModuleCard(
131264
imageVector = getModuleIcon(moduleName = moduleName, projectName = projectName),
132265
contentDescription = moduleName,
133266
tint = getModuleColor(moduleName = moduleName, projectName = projectName),
134-
modifier = Modifier.size(24.dp)
267+
modifier = Modifier
268+
.size(34.dp)
269+
.background(
270+
getModuleColor(moduleName = moduleName, projectName = projectName)
271+
.copy(alpha = 0.12f),
272+
CircleShape
273+
)
274+
.padding(7.dp)
135275
)
136276
Spacer(modifier = Modifier.width(12.dp))
137277
Column(modifier = Modifier.weight(1f)) {
@@ -140,27 +280,48 @@ fun DetailedModuleCard(
140280
.replaceFirstChar { it.uppercase() },
141281
style = MaterialTheme.typography.titleMedium,
142282
fontWeight = FontWeight.Bold,
283+
maxLines = 1,
284+
overflow = TextOverflow.Ellipsis
143285
)
144286
Text(
145-
text = fileName.replaceFirstChar { it.uppercase() },
146-
style = MaterialTheme.typography.bodyMedium,
147-
color = getModuleColor(moduleName = moduleName, projectName = projectName)
287+
text = modulePath.ifBlank { fileName }.replaceFirstChar { it.uppercase() },
288+
style = MaterialTheme.typography.bodySmall,
289+
color = MaterialTheme.colorScheme.onSurfaceVariant,
290+
maxLines = 1,
291+
overflow = TextOverflow.Ellipsis,
292+
textDecoration = TextDecoration.Underline,
293+
modifier = Modifier.pointerHoverIcon(
294+
PointerIcon(
295+
Cursor.getPredefinedCursor(
296+
Cursor.HAND_CURSOR
297+
)
298+
)
299+
)
148300
)
149301
}
150-
Surface(
151-
color = MaterialTheme.colorScheme.primaryContainer,
152-
shape = MaterialTheme.shapes.medium
153-
) {
154-
Text(
155-
text = sizeReadable,
156-
style = MaterialTheme.typography.labelLarge,
157-
fontWeight = FontWeight.Bold,
158-
modifier = Modifier.padding(horizontal = 12.dp, vertical = 6.dp)
159-
)
302+
Column(horizontalAlignment = Alignment.End) {
303+
Surface(
304+
color = MaterialTheme.colorScheme.primaryContainer,
305+
shape = MaterialTheme.shapes.medium
306+
) {
307+
Text(
308+
text = sizeReadable,
309+
style = MaterialTheme.typography.labelLarge,
310+
fontWeight = FontWeight.Bold,
311+
modifier = Modifier.padding(horizontal = 10.dp, vertical = 5.dp)
312+
)
313+
}
314+
if (isRootBuildFile) {
315+
Text(
316+
"Root",
317+
style = MaterialTheme.typography.labelSmall,
318+
color = MaterialTheme.colorScheme.primary,
319+
modifier = Modifier.padding(top = 4.dp)
320+
)
321+
}
160322
}
161323
}
162324

163-
// Module stats in grid
164325
if (plugins.isNotEmpty() || dependencies.isNotEmpty()) {
165326
Row(
166327
modifier = Modifier.fillMaxWidth(),
@@ -437,4 +598,4 @@ fun ModuleRow(
437598
color = MaterialTheme.colorScheme.onSurfaceVariant
438599
)
439600
}
440-
}
601+
}

0 commit comments

Comments
 (0)