Skip to content

Commit 5199638

Browse files
committed
fix(android): re-analyze URL on ScanResultScreen load
ROOT CAUSE: Flags were ALWAYS EMPTY because: 1. Navigation passes url/verdict/score through URL params (persist) 2. flags was read from viewModel.uiState which changes/resets 3. From History: ViewModel state ≠ historical scan 4. Result: flags always emptyList()! FIX: Re-analyze URL when screen loads using 2025 Compose produceState: - Inject PhishingEngine via koinInject() - Call analyze(url) in produceState block - Fresh analysis provides real flags every time Code pattern: val analysisResult by produceState<RiskAssessment?>(null, url) { value = phishingEngine.analyze(url) } val realFlags = analysisResult?.flags ?: emptyList() Version: 1.20.18
1 parent 5c5e656 commit 5199638

4 files changed

Lines changed: 106 additions & 29 deletions

File tree

.agent/agent.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,36 @@ Any important notes for future agents.
185185

186186
---
187187

188+
# 🔧 December 30, 2025 (Session 10k+61) - Re-analyze URL on Screen Load
189+
190+
### Summary
191+
Fixed flags not showing by re-analyzing URL when ScanResultScreen loads using 2025 Compose `produceState` pattern.
192+
193+
## ✅ Root Cause
194+
195+
**Problem:** Flags were ALWAYS EMPTY because:
196+
1. Navigation passes `url`, `verdict`, `score` through URL params (these persist)
197+
2. `flags` was read from `viewModel.uiState` which changes/resets
198+
3. When navigating from History, ViewModel state ≠ historical scan
199+
4. Result: `flags` always `emptyList()`!
200+
201+
**Fix:** Re-analyze URL when screen loads using `produceState`:
202+
```kotlin
203+
val analysisResult by produceState<RiskAssessment?>(null, url) {
204+
value = phishingEngine.analyze(url)
205+
}
206+
```
207+
208+
## 📁 Files Modified
209+
210+
| File | Change |
211+
|------|--------|
212+
| `ScanResultScreen.kt` | Use `produceState` + `koinInject<PhishingEngine>()` |
213+
| `Navigation.kt` | Removed stale flag extraction |
214+
| `CHANGELOG.md` | Version 1.20.18 |
215+
216+
---
217+
188218
# 🔧 December 30, 2025 (Session 10k+60) - Fix Flag Matching Logic
189219

190220
### Summary

CHANGELOG.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,49 @@ All notable changes to QR-SHIELD will be documented in this file.
88

99
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1010

11+
## [1.20.18] - 2025-12-30
12+
13+
### Raouf: Re-analyze URL on ScanResultScreen (2025-12-30 19:45 AEDT)
14+
15+
**Scope:** Fix flags not showing - re-analyze URL when screen loads
16+
17+
**Root Cause:**
18+
- Navigation passes `url`, `verdict`, `score` through URL parameters
19+
- `flags` was being read from ViewModel `uiState` which may not be in `Result` state
20+
- When navigating from History, ViewModel state is NOT the historical scan
21+
- Result: `flags` was always empty!
22+
23+
**Fix Applied (2025 Compose Best Practice):**
24+
- Use `produceState` to call suspend `analyze()` function
25+
- Re-analyze URL when ScanResultScreen loads
26+
- Fresh analysis provides real flags every time
27+
28+
**Code Change:**
29+
```kotlin
30+
// Before: flags passed through navigation (unreliable)
31+
val flags = assessment?.flags ?: emptyList() // ALWAYS EMPTY!
32+
33+
// After: re-analyze URL on screen load (2025 Compose pattern)
34+
val analysisResult by produceState<RiskAssessment?>(null, url) {
35+
value = phishingEngine.analyze(url)
36+
}
37+
val realFlags = analysisResult?.flags ?: emptyList()
38+
```
39+
40+
**Files Modified:**
41+
42+
| File | Change |
43+
|------|--------|
44+
| `ScanResultScreen.kt` | Use `produceState` to call `phishingEngine.analyze(url)` on load |
45+
| `Navigation.kt` | Simplified - removed stale flag extraction |
46+
47+
**Build Verification:**
48+
```bash
49+
./gradlew :androidApp:assembleDebug # BUILD SUCCESSFUL ✅
50+
```
51+
52+
---
53+
1154
## [1.20.17] - 2025-12-30
1255

1356
### Raouf: Fix Flag Matching - Wire ALL Decorative Data to Engine (2025-12-30 19:00 AEDT)

androidApp/src/main/kotlin/com/qrshield/android/ui/navigation/Navigation.kt

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -447,24 +447,11 @@ fun QRShieldNavHost(
447447
val verdict = backStackEntry.arguments?.getString("verdict") ?: "UNKNOWN"
448448
val score = backStackEntry.arguments?.getInt("score") ?: 0
449449

450-
// Extract real flags from current assessment in ViewModel
451-
val uiState by viewModel.uiState.collectAsState()
452-
val assessment = (uiState as? com.qrshield.ui.UiState.Result)?.assessment
453-
val flags = assessment?.flags ?: emptyList()
454-
val brandMatch = assessment?.details?.brandMatch
455-
val tld = assessment?.details?.tld
456-
val confidence = assessment?.confidence ?: 0.8f
457-
val heuristicScore = assessment?.details?.heuristicScore ?: 0
458-
450+
// ScanResultScreen now re-analyzes the URL internally for fresh flags
459451
ScanResultScreen(
460452
url = url,
461453
verdict = verdict,
462454
score = score,
463-
flags = flags,
464-
brandMatch = brandMatch,
465-
tld = tld,
466-
engineConfidence = confidence,
467-
heuristicScore = heuristicScore,
468455
onBackClick = { navController.popBackStack() },
469456
onShareClick = {
470457
val shareText = viewModel.generateShareText()

androidApp/src/main/kotlin/com/qrshield/android/ui/screens/ScanResultScreen.kt

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,12 +63,6 @@ fun ScanResultScreen(
6363
url: String = "https://example.com",
6464
verdict: String = "UNKNOWN",
6565
score: Int = 0,
66-
// Real engine data (not hardcoded!)
67-
flags: List<String> = emptyList(),
68-
brandMatch: String? = null,
69-
tld: String? = null,
70-
engineConfidence: Float = 0.8f,
71-
heuristicScore: Int = 0,
7266
// Callbacks
7367
onBackClick: () -> Unit = {},
7468
onShareClick: () -> Unit = {},
@@ -77,6 +71,29 @@ fun ScanResultScreen(
7771
onCopyUrl: () -> Unit = {},
7872
modifier: Modifier = Modifier
7973
) {
74+
// Get PhishingEngine to re-analyze for fresh flags
75+
val phishingEngine: com.qrshield.core.PhishingEngine = org.koin.compose.koinInject()
76+
77+
// Re-analyze URL to get REAL flags (not stale/empty data from navigation)
78+
// Use produceState to call suspend function
79+
val analysisResult by androidx.compose.runtime.produceState<com.qrshield.model.RiskAssessment?>(
80+
initialValue = null,
81+
key1 = url
82+
) {
83+
value = try {
84+
phishingEngine.analyze(url)
85+
} catch (e: Exception) {
86+
null
87+
}
88+
}
89+
90+
// Use real data from fresh analysis
91+
val realFlags = analysisResult?.flags ?: emptyList()
92+
val realBrandMatch = analysisResult?.details?.brandMatch
93+
val realTld = analysisResult?.details?.tld
94+
val realConfidence = analysisResult?.confidence ?: 0.8f
95+
val realHeuristicScore = analysisResult?.details?.heuristicScore ?: 0
96+
8097
// Derive display values from navigation params
8198
val displayVerdict = when (verdict.uppercase()) {
8299
"MALICIOUS" -> stringResource(R.string.verdict_malicious)
@@ -91,7 +108,7 @@ fun ScanResultScreen(
91108
else -> stringResource(R.string.threat_type_unknown)
92109
}
93110
// Use real engine confidence instead of hardcoded values
94-
val confidence = (engineConfidence * 100).toInt().coerceIn(0, 100)
111+
val confidence = (realConfidence * 100).toInt().coerceIn(0, 100)
95112
val severityScore = score / 10f
96113
val scrollState = rememberScrollState()
97114

@@ -147,17 +164,17 @@ fun ScanResultScreen(
147164

148165
Spacer(modifier = Modifier.height(16.dp))
149166

150-
// Engine Stats - REAL data!
167+
// Engine Stats - REAL data from fresh analysis!
151168
EngineStatsCard(
152-
heuristicScore = heuristicScore,
153-
flagCount = flags.size,
169+
heuristicScore = realHeuristicScore,
170+
flagCount = realFlags.size,
154171
modifier = Modifier.padding(horizontal = 16.dp)
155172
)
156173

157174
Spacer(modifier = Modifier.height(16.dp))
158175

159176
// Tags/Chips - Dynamic from real flags!
160-
TagsRow(flags = flags)
177+
TagsRow(flags = realFlags)
161178

162179
Spacer(modifier = Modifier.height(24.dp))
163180

@@ -179,11 +196,11 @@ fun ScanResultScreen(
179196

180197
Spacer(modifier = Modifier.height(24.dp))
181198

182-
// Analysis Breakdown - REAL engine data, not hardcoded!
199+
// Analysis Breakdown - REAL engine data from fresh analysis!
183200
AnalysisBreakdownSection(
184-
flags = flags,
185-
brandMatch = brandMatch,
186-
tld = tld,
201+
flags = realFlags,
202+
brandMatch = realBrandMatch,
203+
tld = realTld,
187204
modifier = Modifier.padding(horizontal = 16.dp)
188205
)
189206

0 commit comments

Comments
 (0)