Skip to content

Commit cd6a4c5

Browse files
committed
feat: Migrate BrainVisualizer to common module for multiplatform support, update Android minSdk, and bump app version.
1 parent bdcc5f0 commit cd6a4c5

9 files changed

Lines changed: 384 additions & 65 deletions

File tree

.agent/agent.md

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

186186
---
187187

188+
# 🧠 December 24, 2025 (Session 10k+5) - Cross-Platform Brain Visualizer (Desktop Integration)
189+
190+
### Summary
191+
Migrated the "Brain" visualizer from Android-specific code to a shared KMP component (`CommonBrainVisualizer`) and integrated it into the Desktop application's "Beat The Bot" training screen, leveraging the common logic for consistent UI across platforms.
192+
193+
## ✅ Changes Made
194+
195+
### Files Updated
196+
| File | Change |
197+
|------|--------|
198+
| `common/.../ui/components/CommonBrainVisualizer.kt` | **New** Shared KMP component for neural net visualization |
199+
| `desktopApp/.../screens/TrainingScreen.kt` | Integrated `CommonBrainVisualizer` into analysis report |
200+
| `androidApp/.../screens/BeatTheBotScreen.kt` | Refactored to use shared `CommonBrainVisualizer` |
201+
| `androidApp/.../ui/components/BrainVisualizerTest.kt` | Updated tests to check `CommonBrainVisualizer` |
202+
| `androidApp/build.gradle.kts` | Bumped minSdk to 26 (common requirement) & version to 1.17.29 |
203+
| `CHANGELOG.md` | Added 1.17.29 entry |
204+
205+
### Files Deleted
206+
| File | Reason |
207+
|------|--------|
208+
| `androidApp/.../ui/components/BrainVisualizer.kt` | Replaced by Common KMP version |
209+
210+
## 🔧 Technical Details
211+
- **Shared Code:** `CommonBrainVisualizer` uses `Canvas` and `MaterialTheme` (Compose Multiplatform) to work on both Android and Desktop.
212+
- **Desktop Logic:** The visualizer in Desktop mirrors Android's behavior: showing "Calm" (blue) state when safe, and "Pulse" (red) state with badges when threats are detected (based on `training.scenario.insights`).
213+
- **Build Fix:** `androidApp` required `minSdk 26` because the shared module depends on `java.time` APIs via `kotlinx-datetime` or similar mechanisms in common code.
214+
215+
## ✅ Build Verification
216+
```bash
217+
./gradlew :androidApp:assembleDebug :desktopApp:assemble
218+
# OUTPUT: BUILD SUCCESSFUL
219+
```
220+
188221
# 🛠️ December 24, 2025 (Session 10k+4) - Android Test Error Resolution
189222

190223
### Summary

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ All notable changes to QR-SHIELD will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.17.29] - 2025-12-24
9+
10+
### 🧠 Cross-Platform Brain Visualizer
11+
12+
Migrated the "Brain" visualizer to a shared KMP component and integrated it into the Desktop application, ensuring identical "Beat The Bot" experience across Android and Desktop.
13+
14+
#### ✨ Features
15+
- **CommonBrainVisualizer:** Refactored Android-specific implementation into a pure KMP `CommonBrainVisualizer` in `commonMain`.
16+
- **Desktop Integration:** Enhanced `TrainingScreen.kt` with the brain visualizer in the analysis report section.
17+
- **Dynamic Signals:** Desktop visualizer now reacts to training scenario insights (Good/Bad signals).
18+
19+
#### 🔧 Technical & Builds
20+
- **minSdk Update:** Bumped `androidApp` minSdk to 26 to match `common` module requirements.
21+
- **Test Updates:** Updated `BrainVisualizerTest` to verify the shared component.
22+
- **Cleanup:** Removed platform-specific `BrainVisualizer.kt` from Android.
23+
824
## [1.17.27] - 2025-12-24
925

1026
### 🎮 Beat The Bot Training - Visual Upgrades

androidApp/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,10 @@ android {
1414

1515
defaultConfig {
1616
applicationId = "com.qrshield.android"
17-
minSdk = 24
17+
minSdk = 26
1818
targetSdk = 35 // Android 16
19-
versionCode = 15
20-
versionName = "1.17.28"
19+
versionCode = 16
20+
versionName = "1.17.29"
2121

2222
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
2323
}

androidApp/src/androidTest/kotlin/com/qrshield/android/ui/components/BrainVisualizerTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.qrshield.android.ui.components
33
import androidx.compose.ui.test.*
44
import androidx.compose.ui.test.junit4.createComposeRule
55
import androidx.test.ext.junit.runners.AndroidJUnit4
6+
import com.qrshield.ui.components.CommonBrainVisualizer
67
import org.junit.Rule
78
import org.junit.Test
89
import org.junit.runner.RunWith
@@ -16,7 +17,7 @@ class BrainVisualizerTest {
1617
@Test
1718
fun brainVisualizer_showsIdleState_whenSignalsEmpty() {
1819
composeTestRule.setContent {
19-
BrainVisualizer(detectedSignals = emptyList())
20+
CommonBrainVisualizer(detectedSignals = emptyList())
2021
}
2122

2223
// Check content description for idle state (Blue brain)
@@ -28,7 +29,7 @@ class BrainVisualizerTest {
2829
fun brainVisualizer_showsBadges_whenSignalsProvided() {
2930
val signals = listOf("TLD_ABUSE", "BRAND_IMPERSONATION")
3031
composeTestRule.setContent {
31-
BrainVisualizer(detectedSignals = signals)
32+
CommonBrainVisualizer(detectedSignals = signals)
3233
}
3334

3435
// Check content description for active state (Red pulsing brain)

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import com.qrshield.android.ui.theme.QRShieldColors
4343
import com.qrshield.android.ui.theme.QRShieldShapes
4444
import com.qrshield.android.ui.viewmodels.GameResult
4545
import com.qrshield.android.ui.viewmodels.GameState
46-
import com.qrshield.android.ui.components.BrainVisualizer
46+
import com.qrshield.ui.components.CommonBrainVisualizer
4747
import kotlin.random.Random
4848

4949
/**
@@ -649,7 +649,7 @@ private fun RoundAnalysisCard(
649649
// Use the new BrainVisualizer component
650650
// We pass signals ONLY if it's phishing. If it's safe, we pass empty list for "Calm" brain.
651651
val visualSignals = if (isPhishing) signals else emptyList()
652-
BrainVisualizer(
652+
CommonBrainVisualizer(
653653
detectedSignals = visualSignals,
654654
modifier = Modifier.fillMaxWidth()
655655
)

androidApp/src/main/kotlin/com/qrshield/android/ui/viewmodels/BeatTheBotViewModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ data class GameUrl(
2727
val url: String,
2828
val isPhishing: Boolean,
2929
val context: String,
30-
val sender: String = "+1 (555) 123-4567"
30+
val sender: String = "+1 (555) 123-4567",
31+
val signals: List<String> = emptyList()
3132
)
3233

3334
enum class GameResult {

androidApp/src/main/kotlin/com/qrshield/android/ui/components/BrainVisualizer.kt renamed to common/src/commonMain/kotlin/com/qrshield/ui/components/CommonBrainVisualizer.kt

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,35 @@
1-
package com.qrshield.android.ui.components
1+
package com.qrshield.ui.components
22

33
import androidx.compose.animation.core.*
44
import androidx.compose.foundation.Canvas
55
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.shape.RoundedCornerShape
67
import androidx.compose.material3.MaterialTheme
8+
import androidx.compose.material3.Surface
79
import androidx.compose.material3.Text
810
import androidx.compose.runtime.*
911
import androidx.compose.ui.Alignment
1012
import androidx.compose.ui.Modifier
1113
import androidx.compose.ui.geometry.Offset
1214
import androidx.compose.ui.graphics.Color
13-
import androidx.compose.ui.graphics.drawscope.DrawScope
1415
import androidx.compose.ui.graphics.graphicsLayer
1516
import androidx.compose.ui.semantics.contentDescription
1617
import androidx.compose.ui.semantics.semantics
1718
import androidx.compose.ui.unit.dp
18-
import com.qrshield.android.ui.theme.QRShieldColors
19-
import kotlinx.coroutines.launch
20-
import kotlin.math.cos
2119
import kotlin.math.sin
2220
import kotlin.random.Random
2321

2422
/**
2523
* A "Brain" visualization that lights up specific neural clusters based on detected signals.
2624
* Designed for immediate visual feedback in the "Beat The Bot" game.
2725
*
26+
* This component is shared between Android and Desktop (KMP).
27+
*
2828
* @param detectedSignals List of signal strings (e.g. "TLD_ABUSE", "BRAND_IMPERSONATION")
2929
* @param modifier Modifier for layout
3030
*/
3131
@Composable
32-
fun BrainVisualizer(
32+
fun CommonBrainVisualizer(
3333
detectedSignals: List<String>,
3434
modifier: Modifier = Modifier
3535
) {
@@ -91,9 +91,9 @@ private fun BrainCanvas(
9191
mapSignalsToNodes(signals, nodes.size)
9292
}
9393

94-
// Color definitions
95-
val safeColor = QRShieldColors.Primary
96-
val dangerColor = QRShieldColors.Red500
94+
// Color definitions from Material Theme for KMP compatibility
95+
val safeColor = MaterialTheme.colorScheme.primary
96+
val dangerColor = MaterialTheme.colorScheme.error
9797
val inactiveColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
9898

9999
Canvas(modifier = modifier) {
@@ -176,24 +176,29 @@ private fun BrainCanvas(
176176

177177
@Composable
178178
private fun DetectedSignalsBadges(signals: List<String>) {
179+
val errorColor = MaterialTheme.colorScheme.error
180+
181+
// Use FlowRow if available in newer Compose, but Row is safer for older versions
182+
// For now, we'll wrap in a Row with horizontal scrolling if needed, or simple wrapping isn't easy without FlowRow
183+
// Let's use a simple Row for MVP centered
179184
Row(
180185
modifier = Modifier
181186
.fillMaxWidth()
182187
.padding(horizontal = 16.dp),
183188
horizontalArrangement = Arrangement.Center
184189
) {
185190
signals.forEach { signal ->
186-
androidx.compose.material3.Surface(
187-
color = QRShieldColors.Red500.copy(alpha = 0.1f),
188-
shape = androidx.compose.foundation.shape.RoundedCornerShape(4.dp),
189-
border = androidx.compose.foundation.BorderStroke(1.dp, QRShieldColors.Red500.copy(alpha = 0.3f)),
191+
Surface(
192+
color = errorColor.copy(alpha = 0.1f),
193+
shape = RoundedCornerShape(4.dp),
194+
border = androidx.compose.foundation.BorderStroke(1.dp, errorColor.copy(alpha = 0.3f)),
190195
modifier = Modifier.padding(4.dp)
191196
) {
192197
Text(
193198
text = signal.replace("_", " "),
194199
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
195200
style = MaterialTheme.typography.labelSmall,
196-
color = QRShieldColors.Red500
201+
color = errorColor
197202
)
198203
}
199204
}

0 commit comments

Comments
 (0)