Skip to content

Commit c7737a8

Browse files
committed
feat: add dependency analysis workspace with Gradle version variable resolution and multi-pane UI layout
1 parent 4b616de commit c7737a8

2 files changed

Lines changed: 221 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.meet.dev.analyzer.data.repository.project.helpers
2+
3+
import com.meet.dev.analyzer.data.models.project.ModuleBuildFileInfo
4+
5+
class GradleVersionResolver {
6+
private val extVars = mutableMapOf<String, String>()
7+
8+
fun collectExtVariables(moduleBuildFileInfos: List<ModuleBuildFileInfo>) {
9+
val extRegex = Regex("""ext\.([a-zA-Z0-9_]+)\s*=\s*['"]([^'"]+)['"]""")
10+
val extBlockRegex = Regex("""ext\s*\{([^}]+)\}""", RegexOption.DOT_MATCHES_ALL)
11+
val varAssignRegex = Regex("""([a-zA-Z0-9_]+)\s*=\s*['"]([^'"]+)['"]""")
12+
13+
moduleBuildFileInfos.forEach { fileInfo ->
14+
// Simple ext.var = 'val'
15+
extRegex.findAll(fileInfo.content).forEach { match ->
16+
extVars[match.groupValues[1]] = match.groupValues[2]
17+
}
18+
19+
// ext { ... } block
20+
extBlockRegex.findAll(fileInfo.content).forEach { blockMatch ->
21+
val blockContent = blockMatch.groupValues[1]
22+
varAssignRegex.findAll(blockContent).forEach { match ->
23+
extVars[match.groupValues[1]] = match.groupValues[2]
24+
}
25+
}
26+
}
27+
}
28+
29+
fun resolve(version: String?): String? {
30+
if (version == null) return null
31+
32+
// Handle $variable or ${variable}
33+
if (version.contains("$")) {
34+
var resolved = version
35+
val varRegex = Regex("""\$([a-zA-Z0-9_]+)|\$\{([a-zA-Z0-9_]+)\}""")
36+
varRegex.findAll(version).forEach { match ->
37+
val varName = match.groupValues[1].ifEmpty { match.groupValues[2] }
38+
extVars[varName]?.let {
39+
resolved = resolved.replace(match.value, it)
40+
}
41+
}
42+
return resolved
43+
}
44+
45+
// Handle plain variable name if it's exactly a variable (common in Groovy)
46+
return extVars[version] ?: version
47+
}
48+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package com.meet.dev.analyzer.presentation.screen.workspace.components
2+
3+
import androidx.compose.foundation.layout.*
4+
import androidx.compose.foundation.lazy.LazyColumn
5+
import androidx.compose.foundation.lazy.items
6+
import androidx.compose.foundation.lazy.rememberLazyListState
7+
import androidx.compose.foundation.rememberScrollbarAdapter
8+
import androidx.compose.material.icons.Icons
9+
import androidx.compose.material.icons.automirrored.filled.Sort
10+
import androidx.compose.material.icons.filled.Check
11+
import androidx.compose.material.icons.filled.Search
12+
import androidx.compose.material3.*
13+
import androidx.compose.runtime.*
14+
import androidx.compose.ui.Alignment
15+
import androidx.compose.ui.Modifier
16+
import androidx.compose.ui.input.pointer.PointerIcon
17+
import androidx.compose.ui.input.pointer.pointerHoverIcon
18+
import androidx.compose.ui.text.font.FontWeight
19+
import androidx.compose.ui.unit.dp
20+
import com.meet.dev.analyzer.presentation.components.CustomOutlinedTextField
21+
import com.meet.dev.analyzer.presentation.components.VerticalScrollBarLayout
22+
import com.meet.dev.analyzer.presentation.screen.workspace.WorkspaceIntent
23+
import com.meet.dev.analyzer.presentation.screen.workspace.WorkspaceUiState
24+
import java.awt.Cursor
25+
26+
@Composable
27+
fun DetectedProjectsPane(
28+
uiState: WorkspaceUiState,
29+
onIntent: (WorkspaceIntent) -> Unit,
30+
modifier: Modifier = Modifier
31+
) {
32+
var projectSortOrder by remember { mutableStateOf(ProjectSortOrder.NAME_ASC) }
33+
val projectListState = rememberLazyListState()
34+
35+
val filteredProjects = remember(uiState.projects, uiState.searchQuery, projectSortOrder) {
36+
val filtered = uiState.projects.filter {
37+
it.projectName.contains(uiState.searchQuery, ignoreCase = true)
38+
}
39+
when (projectSortOrder) {
40+
ProjectSortOrder.NAME_ASC -> filtered.sortedBy { it.projectName.lowercase() }
41+
ProjectSortOrder.NAME_DESC -> filtered.sortedByDescending { it.projectName.lowercase() }
42+
ProjectSortOrder.SIZE_DESC -> filtered.sortedByDescending { it.totalSizeBytes }
43+
ProjectSortOrder.SIZE_ASC -> filtered.sortedBy { it.totalSizeBytes }
44+
}
45+
}
46+
47+
Column(
48+
modifier = modifier.fillMaxHeight()
49+
) {
50+
Text(
51+
"Detected Projects (${uiState.projects.size})",
52+
style = MaterialTheme.typography.titleMedium,
53+
fontWeight = FontWeight.Bold,
54+
modifier = Modifier.padding(bottom = 8.dp)
55+
)
56+
57+
var showSortMenu by remember { mutableStateOf(false) }
58+
59+
Row(
60+
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
61+
horizontalArrangement = Arrangement.spacedBy(8.dp),
62+
verticalAlignment = Alignment.CenterVertically
63+
) {
64+
CustomOutlinedTextField(
65+
value = uiState.searchQuery,
66+
onValueChange = { onIntent(WorkspaceIntent.OnSearchQueryChange(it)) },
67+
onClear = { onIntent(WorkspaceIntent.OnSearchQueryChange("")) },
68+
modifier = Modifier.weight(1f),
69+
leadingIcon = Icons.Default.Search,
70+
labelText = "Search Projects",
71+
placeholder = { Text("Search projects by name...") }
72+
)
73+
74+
Box {
75+
IconButton(
76+
modifier = Modifier.pointerHoverIcon(
77+
PointerIcon(
78+
Cursor.getPredefinedCursor(
79+
Cursor.HAND_CURSOR
80+
)
81+
)
82+
),
83+
onClick = { showSortMenu = true }
84+
) {
85+
Icon(
86+
imageVector = Icons.AutoMirrored.Filled.Sort,
87+
contentDescription = "Sort Projects",
88+
tint = MaterialTheme.colorScheme.primary
89+
)
90+
}
91+
92+
DropdownMenu(
93+
expanded = showSortMenu,
94+
onDismissRequest = { showSortMenu = false }
95+
) {
96+
DropdownMenuItem(
97+
text = { Text("Name (A-Z)") },
98+
onClick = {
99+
projectSortOrder = ProjectSortOrder.NAME_ASC
100+
showSortMenu = false
101+
},
102+
leadingIcon = {
103+
if (projectSortOrder == ProjectSortOrder.NAME_ASC) {
104+
Icon(Icons.Default.Check, contentDescription = null)
105+
}
106+
}
107+
)
108+
DropdownMenuItem(
109+
text = { Text("Name (Z-A)") },
110+
onClick = {
111+
projectSortOrder = ProjectSortOrder.NAME_DESC
112+
showSortMenu = false
113+
},
114+
leadingIcon = {
115+
if (projectSortOrder == ProjectSortOrder.NAME_DESC) {
116+
Icon(Icons.Default.Check, contentDescription = null)
117+
}
118+
}
119+
)
120+
DropdownMenuItem(
121+
text = { Text("Size (Largest)") },
122+
onClick = {
123+
projectSortOrder = ProjectSortOrder.SIZE_DESC
124+
showSortMenu = false
125+
},
126+
leadingIcon = {
127+
if (projectSortOrder == ProjectSortOrder.SIZE_DESC) {
128+
Icon(Icons.Default.Check, contentDescription = null)
129+
}
130+
}
131+
)
132+
DropdownMenuItem(
133+
text = { Text("Size (Smallest)") },
134+
onClick = {
135+
projectSortOrder = ProjectSortOrder.SIZE_ASC
136+
showSortMenu = false
137+
},
138+
leadingIcon = {
139+
if (projectSortOrder == ProjectSortOrder.SIZE_ASC) {
140+
Icon(Icons.Default.Check, contentDescription = null)
141+
}
142+
}
143+
)
144+
}
145+
}
146+
}
147+
148+
BoxWithConstraints(modifier = Modifier.weight(1f)) {
149+
LazyColumn(
150+
state = projectListState,
151+
modifier = Modifier.fillMaxSize().padding(end = 12.dp),
152+
verticalArrangement = Arrangement.spacedBy(8.dp)
153+
) {
154+
items(filteredProjects) { project ->
155+
val isHighlighted =
156+
project.projectName == uiState.highlightedProjectName
157+
ProjectInfoCard(
158+
project = project,
159+
isHighlighted = isHighlighted
160+
)
161+
}
162+
}
163+
VerticalScrollBarLayout(adapter = rememberScrollbarAdapter(projectListState))
164+
}
165+
}
166+
}
167+
168+
enum class ProjectSortOrder {
169+
NAME_ASC,
170+
NAME_DESC,
171+
SIZE_DESC,
172+
SIZE_ASC
173+
}

0 commit comments

Comments
 (0)