Skip to content

Feature/new architecture#2

Closed
cyclonite69 wants to merge 17 commits intomasterfrom
feature/new-architecture
Closed

Feature/new architecture#2
cyclonite69 wants to merge 17 commits intomasterfrom
feature/new-architecture

Conversation

@cyclonite69
Copy link
Copy Markdown
Owner

@cyclonite69 cyclonite69 commented Mar 29, 2026

Description

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactoring (no functional changes, code improvements)
  • Documentation update
  • Test additions/updates

Related Issues

Closes #

Changes Made

Testing Performed

  • Unit tests added/updated
  • All unit tests pass locally
  • Manual testing on device/emulator
  • Tested on multiple Android versions (if applicable)
  • Tested edge cases

Screenshots (if applicable)

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published

Code Quality

  • Code passes detekt checks
  • No hardcoded API keys or secrets
  • Follows Clean Architecture patterns
  • Uses Hilt dependency injection
  • Proper error handling implemented

Additional Notes


Summary by cubic

Rearchitected the app into a modular MVVM stack with Hilt DI, shared core models, and a rebuilt scanner pipeline. This improves boundaries, testability, and adds session-based telemetry with better performance.

  • New Features

    • Introduced multi-module architecture: :core, :domain, :data, and modular WiFi (com.shadowcheck.mobile.wifi).
    • Rebuilt CompleteScannerService with session tracking, location samples, BLE/classic BT/cell capture, and power‑aware throttling.
    • Added feature ViewModels (lists, details, heatmap, stats, threat detection, map) and wired routes in MainActivity.
    • Added debug-only EmulatorHelper and MockScannerService for quick data seeding on emulators.
    • Set up CI (.github/workflows/ci.yml), .editorconfig, and contributor docs.
  • Refactors

    • Consolidated to com.shadowcheck.mobile.core.model and removed duplicate app-layer domain models and repositories.
    • Expanded Room schema (BLE, geofence, sensor, sessions, location) with new DAOs and Hilt bindings (BleModule, SensorDataModule, SessionDataModule, etc.).
    • Refactored SurveillanceDetector for DI and modular WiFi models; removed legacy screens like ARNetworkView.
    • Upgraded compileSdk to 35, added proguard-rules.pro, and updated .gitignore.
    • Split WiFi into a dedicated data stack (local DAO and WiGLEApiService) with WifiRepositoryModule.

Written for commit 284aee5. Summary will update on new commits.

- Add core, domain, and data modules for better separation of concerns
- Migrate UI screens to use ViewModels and StateFlow for robust state management
- Integrate Hilt for dependency injection across the application
- Add comprehensive unit tests for Use Cases and ViewModels
- Refactor SurveillanceDetector for DI and clean architecture
- Fix duplicate module inclusions in settings.gradle.kts
- Add .claude/ to .gitignore and include project documentation
- Add core, domain, and data modules for better separation of concerns
- Migrate UI screens to use ViewModels and StateFlow for robust state management
- Integrate Hilt for dependency injection across the application
- Add comprehensive unit tests for Use Cases and ViewModels
- Refactor SurveillanceDetector for DI and clean architecture
- Fix duplicate module inclusions in settings.gradle.kts
- Add .claude/ to .gitignore and include project documentation
- Add core, domain, and data modules for better separation of concerns
- Migrate UI screens to use ViewModels and StateFlow for robust state management
- Integrate Hilt for dependency injection across the application
- Add comprehensive unit tests for Use Cases and ViewModels
- Refactor SurveillanceDetector for DI and clean architecture
- Fix duplicate module inclusions in settings.gradle.kts
- Add .claude/ to .gitignore and include project documentation
- Remove duplicate app-layer domain models
- Point all active consumers to core.model and domain.repository
- Update test utilities to match
- Route wifi/bluetooth/cellular nav entries to rebuilt list screens
- Migrate NetworkFinderScreen to modular WifiNetwork model
- Migrate CompleteScannerService WiFi and classic Bluetooth paths
  to shared domain repositories
- NOTE: BLE scan path and CellularTower write path in
  CompleteScannerService still use legacy DAO (tracked for next session)
- Scale scan cadence by queue pressure and flush duration
- Respect battery saver state when not charging
- Surface throttle and power state in scanner notification
- Persist location samples and scan sessions in the rebuilt architecture
- Tag WiFi/BLE/Bluetooth/Cellular sightings with session IDs
- Render trajectory breadcrumbs and mission-scoped playback in the rebuilt map
- Surface diagnostics for collection pressure and playback context
- Add aggregate-plus-history Bluetooth and Cellular detail screens
- Add detail ViewModels and history queries by MAC address / cell ID
- Wire rebuilt list screens and nav routes to the new detail screens
- Delete legacy screens and components with no remaining rebuilt call sites
- Keep shared legacy detail helper composables in a standalone file
- Preserve compileability while leaving ChannelAnalysisScreen in place
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Important

Review skipped

Too many files!

This PR contains 197 files, which is 47 over the limit of 150.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33c26354-04ee-4ac0-963b-1a6bb1dd68b7

📥 Commits

Reviewing files that changed from the base of the PR and between 01cb75a and 284aee5.

📒 Files selected for processing (197)
  • .editorconfig
  • .github/CONTRIBUTING.md
  • .github/ISSUE_TEMPLATE/bug_report.md
  • .github/ISSUE_TEMPLATE/feature_request.md
  • .github/pull_request_template.md
  • .github/workflows/ci.yml
  • .gitignore
  • AGENTS.md
  • CMakeLists.txt
  • CONTRIBUTING.md
  • FINAL_STATUS.md
  • GEMINI.md
  • MODULARIZATION_COMPLETE.md
  • MODULE_PROGRESS.md
  • MODULE_STRUCTURE.md
  • MVVM_COMPLETE.md
  • MVVM_REFACTORING.md
  • QUICK_START_EMULATOR.md
  • REFACTORING_SUMMARY.txt
  • app/build.gradle.kts
  • app/proguard-rules.pro
  • app/src/debug/kotlin/com/shadowcheck/mobile/service/MockScannerService.kt
  • app/src/debug/kotlin/com/shadowcheck/mobile/util/EmulatorHelper.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ARNetworkView.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/Daos.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/AppDatabase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/BleDeviceDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/BluetoothDeviceDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/CellularTowerDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/GeofenceDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/HardwareMetadataDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/LocationSampleDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/ScanSessionDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/dao/SensorReadingDao.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/BleDeviceEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/BluetoothDeviceEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/CellularTowerEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/GeofenceEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/HardwareMetadataEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/LocationSampleEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/ScanSessionEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/SensorReadingEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/WifiNetworkEntity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/BleDeviceRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/BluetoothDeviceRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/CellularTowerRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/GeofenceRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/HardwareMetadataRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/LocationSampleRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/ScanSessionRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/SensorReadingRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/data/repository/SensorRepositoryImpl.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/BleModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/DatabaseModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/DispatchersModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/DomainModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/GeofenceModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/LocationDataModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/RepositoryModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/SensorDataModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/SensorModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/SessionDataModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/WifiDataModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/di/WifiRepositoryModule.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/BluetoothDevice.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/CellularTower.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/HeatmapData.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/NetworkResult.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/SurveillanceDetector.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/model/WifiNetwork.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/repository/BluetoothDeviceRepository.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/repository/CellularTowerRepository.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/repository/WifiNetworkRepository.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllWifiNetworksUseCase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetNearbyBluetoothDevicesUseCase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetTowersByLocationUseCase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/SearchWifiNetworksUseCase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/SyncWiGLEUseCase.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/models/CellularFilters.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/BluetoothDetailViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/BluetoothListViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/BluetoothViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/CellularDetailViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/CellularListViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/CellularViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ChannelDistributionViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/GeofenceViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/HeatmapViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/HomeViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/MainViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/MapViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/NetworkDetailViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/SettingsViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/StatsViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ThreatDetectionViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/WifiListViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/WifiViewModel.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/presentation/MainActivity.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/service/CompleteScannerService.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/service/SensorCollectionService.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/BluetoothDetailScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/BluetoothListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/CellularDetailScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/CellularListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/ChannelDistributionScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/HeatmapScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/HomeScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/MapScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/NetworkDetailScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/SettingsScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/ThreatDetectionScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/rebuilt/ui/screens/WiFiListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/service/NetworkMonitorService.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/components/SelectableNetworkList.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/StatsScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/details/LegacyDetailComponents.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/details/NetworkDetailsScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/finder/NetworkFinderScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/lists/BluetoothListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/lists/CellularListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/lists/NetworkListScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/maps/GoogleMapScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/security/GeofenceScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/ui/screens/security/RogueAPScreen.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/utils/DeduplicationUtil.kt
  • app/src/main/kotlin/com/shadowcheck/mobile/utils/ExportUtils.kt
  • app/src/test/kotlin/README.md
  • app/src/test/kotlin/com/shadowcheck/mobile/TestUtils.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/data/repository/WifiNetworkRepositoryImplTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllBluetoothDevicesUseCaseTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllCellularTowersUseCaseTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllWifiNetworksUseCaseTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/domain/usecase/SearchWifiNetworksUseCaseTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/presentation/viewmodel/BluetoothViewModelTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/presentation/viewmodel/CellularViewModelTest.kt
  • app/src/test/kotlin/com/shadowcheck/mobile/presentation/viewmodel/WifiViewModelTest.kt
  • build.gradle.kts
  • core/build.gradle.kts
  • core/src/main/kotlin/com/shadowcheck/mobile/core/di/DispatcherQualifiers.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/BleDevice.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/BluetoothDevice.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/CellularTower.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/Geofence.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/HardwareMetadata.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/HeatmapData.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/LocationSample.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/NetworkResult.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/RadioType.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/ScanSession.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/SensorReading.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/SurveillanceDetector.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/ThreatDetection.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/ThreatSeverity.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/ThreatType.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/UnifiedSighting.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/model/WifiNetwork.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/core/util/Result.kt
  • core/src/main/kotlin/com/shadowcheck/mobile/wifi/model/WifiNetwork.kt
  • data/build.gradle.kts
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/local/WifiDatabase.kt
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/local/dao/WifiNetworkDao.kt
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/local/entity/WifiNetworkEntity.kt
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/remote/WiGLEApiService.kt
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/remote/dto/WigleWifiSearchResponse.kt
  • data/src/main/kotlin/com/shadowcheck/mobile/wifi/data/repository/WifiNetworkRepositoryImpl.kt
  • detekt.yml
  • docs/ANDROID_EMULATOR_SETUP.md
  • docs/JDK_CONFIGURATION.md
  • domain/build.gradle.kts
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/BleDeviceRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/BluetoothDeviceRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/CellularTowerRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/GeofenceRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/HardwareMetadataRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/LocationSampleRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/ScanSessionRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/SensorReadingRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/SensorRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/repository/WifiNetworkRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllBluetoothDevicesUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllCellularTowersUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllScanSessionsUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetAllWifiNetworksUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetNearbyBluetoothDevicesUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetRecentLocationSamplesUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/GetTowersByLocationUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/SearchWifiNetworksUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/domain/usecase/SyncWiGLEUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/wifi/domain/repository/WifiNetworkRepository.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/wifi/domain/usecase/GetAllWifiNetworksUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/wifi/domain/usecase/SearchWifiNetworksUseCase.kt
  • domain/src/main/kotlin/com/shadowcheck/mobile/wifi/domain/usecase/SyncWiGLEUseCase.kt
  • fix-jdk.sh
  • gradle.properties
  • local.properties.example
  • settings.gradle.kts
  • setup-emulator.sh

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/new-architecture

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment on lines +41 to +51
startScanning()
}

fun toggleScanning() {
_uiState.update { it.copy(isScanning = !it.isScanning) }
if (_uiState.value.isScanning) {
startScanning()
}
}

private fun startScanning() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The ViewModel's init block starts a scanning coroutine without setting isScanning to true, causing state inconsistency and subsequent coroutine leaks when scanning is toggled by the user.
Severity: HIGH

Suggested Fix

Store the Job from the launched coroutine in a variable. In startScanning(), cancel any existing Job before launching a new one. Ensure _uiState is updated to set isScanning to true when scanning starts and false when it stops. Consider creating a separate stopScanning() function to handle cancellation and state updates, and call it when the scan is toggled off.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location:
app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ThreatDetectionViewModel.kt#L41-L51

Potential issue: The `ThreatDetectionViewModel`'s `init` block calls `startScanning()`,
which launches a coroutine to collect data, but the UI state `isScanning` remains
`false`. This creates an initial state where the app is actively scanning but the UI
indicates it is not. When the user taps the start button, `toggleScanning()` calls
`startScanning()` again, launching a second coroutine without cancelling the first. Each
subsequent toggle from off to on launches another coroutine, leading to an accumulation
of concurrent jobs that consume CPU and memory and can cause erratic UI updates.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8 issues found across 197 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/WifiNetworkEntity.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/WifiNetworkEntity.kt:15">
P1: Add a new Room migration (and bump the DB version) to add the new wifi_networks columns; otherwise existing users will hit a schema mismatch at startup.</violation>
</file>

<file name="app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ChannelDistributionViewModel.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ChannelDistributionViewModel.kt:35">
P2: Handle 2484 MHz as channel 14 explicitly; the current formula maps it to 15.</violation>
</file>

<file name="app/src/debug/kotlin/com/shadowcheck/mobile/service/MockScannerService.kt">

<violation number="1" location="app/src/debug/kotlin/com/shadowcheck/mobile/service/MockScannerService.kt:35">
P2: startMockScanning() is invoked on every onStartCommand call without checking if a scan job is already running, so multiple service starts will spawn overlapping loops and duplicate inserts. Track a Job/flag and skip if already active.</violation>
</file>

<file name="app/src/main/kotlin/com/shadowcheck/mobile/domain/model/SurveillanceDetector.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/domain/model/SurveillanceDetector.kt:16">
P2: The 2-argument detectThreats no longer sorts by severity, so callers now receive detections in collection order rather than priority order.</violation>
</file>

<file name="MVVM_REFACTORING.md">

<violation number="1" location="MVVM_REFACTORING.md:1">
P3: The doc marks the refactor as completed/100% compliant but later lists multiple screens still using Room.databaseBuilder. Update the completion claim to reflect the remaining work so the status isn’t misleading.</violation>
</file>

<file name="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/LocationSampleEntity.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/LocationSampleEntity.kt:10">
P2: Add an index for sessionId since queries filter on it; otherwise getSamplesForSession will scan the whole table as it grows.</violation>
</file>

<file name="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/HardwareMetadataEntity.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/HardwareMetadataEntity.kt:7">
P2: `upsert` uses REPLACE but macAddress isn’t unique, so inserting the same device will create duplicate rows instead of updating the existing one. Add a unique index (or make macAddress the primary key) so REPLACE works as intended.</violation>
</file>

<file name="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/BluetoothDeviceEntity.kt">

<violation number="1" location="app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/BluetoothDeviceEntity.kt:14">
P1: These new bluetooth_devices columns require a Room schema version bump and a migration to add them (with defaults). Without it, existing installs will fail schema validation or crash on open.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

val level: Int,
val timestamp: Long
val timestamp: Long,
val latitude: Double = 0.0,
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Add a new Room migration (and bump the DB version) to add the new wifi_networks columns; otherwise existing users will hit a schema mismatch at startup.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/WifiNetworkEntity.kt, line 15:

<comment>Add a new Room migration (and bump the DB version) to add the new wifi_networks columns; otherwise existing users will hit a schema mismatch at startup.</comment>

<file context>
@@ -11,9 +11,94 @@ data class WifiNetworkEntity(
     val level: Int,
-    val timestamp: Long
+    val timestamp: Long,
+    val latitude: Double = 0.0,
+    val longitude: Double = 0.0,
+    val channel: Int = 0,
</file context>
Fix with Cubic

val rssi: Int,
val timestamp: Long
val timestamp: Long,
val sessionId: String = "",
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: These new bluetooth_devices columns require a Room schema version bump and a migration to add them (with defaults). Without it, existing installs will fail schema validation or crash on open.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/BluetoothDeviceEntity.kt, line 14:

<comment>These new bluetooth_devices columns require a Room schema version bump and a migration to add them (with defaults). Without it, existing installs will fail schema validation or crash on open.</comment>

<file context>
@@ -2,17 +2,59 @@ package com.shadowcheck.mobile.data.database.model
     val rssi: Int,
-    val timestamp: Long
+    val timestamp: Long,
+    val sessionId: String = "",
+    val latitude: Double = 0.0,
+    val longitude: Double = 0.0,
</file context>
Fix with Cubic

.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ChannelDistributionUiState())

private fun getChannelFromFreq(freq: Int): Int? = when (freq) {
in 2412..2484 -> (freq - 2412) / 5 + 1
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Handle 2484 MHz as channel 14 explicitly; the current formula maps it to 15.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/presentation/viewmodel/ChannelDistributionViewModel.kt, line 35:

<comment>Handle 2484 MHz as channel 14 explicitly; the current formula maps it to 15.</comment>

<file context>
@@ -0,0 +1,39 @@
+        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ChannelDistributionUiState())
+
+    private fun getChannelFromFreq(freq: Int): Int? = when (freq) {
+        in 2412..2484 -> (freq - 2412) / 5 + 1
+        in 5170..5825 -> (freq - 5170) / 5 + 34
+        else -> null
</file context>
Fix with Cubic


override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "MockScannerService started")
startMockScanning()
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: startMockScanning() is invoked on every onStartCommand call without checking if a scan job is already running, so multiple service starts will spawn overlapping loops and duplicate inserts. Track a Job/flag and skip if already active.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/debug/kotlin/com/shadowcheck/mobile/service/MockScannerService.kt, line 35:

<comment>startMockScanning() is invoked on every onStartCommand call without checking if a scan job is already running, so multiple service starts will spawn overlapping loops and duplicate inserts. Track a Job/flag and skip if already active.</comment>

<file context>
@@ -0,0 +1,192 @@
+
+    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
+        Log.d(TAG, "MockScannerService started")
+        startMockScanning()
+        return START_STICKY
+    }
</file context>
Fix with Cubic

Comment on lines +16 to 19
return buildList {
addAll(detectRogueAccessPoints(wifiNetworks))
addAll(detectSuspiciousBluetoothDevices(btDevices))
}
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The 2-argument detectThreats no longer sorts by severity, so callers now receive detections in collection order rather than priority order.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/domain/model/SurveillanceDetector.kt, line 16:

<comment>The 2-argument detectThreats no longer sorts by severity, so callers now receive detections in collection order rather than priority order.</comment>

<file context>
@@ -1,228 +1,120 @@
-                    )
-                )
-            }
+        return buildList {
+            addAll(detectRogueAccessPoints(wifiNetworks))
+            addAll(detectSuspiciousBluetoothDevices(btDevices))
</file context>
Suggested change
return buildList {
addAll(detectRogueAccessPoints(wifiNetworks))
addAll(detectSuspiciousBluetoothDevices(btDevices))
}
return buildList {
addAll(detectRogueAccessPoints(wifiNetworks))
addAll(detectSuspiciousBluetoothDevices(btDevices))
}.sortedByDescending { it.severity.ordinal }
Fix with Cubic


@Entity(
tableName = "location_samples",
indices = [Index(value = ["timestamp"])]
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Add an index for sessionId since queries filter on it; otherwise getSamplesForSession will scan the whole table as it grows.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/LocationSampleEntity.kt, line 10:

<comment>Add an index for sessionId since queries filter on it; otherwise getSamplesForSession will scan the whole table as it grows.</comment>

<file context>
@@ -0,0 +1,53 @@
+
+@Entity(
+    tableName = "location_samples",
+    indices = [Index(value = ["timestamp"])]
+)
+data class LocationSampleEntity(
</file context>
Fix with Cubic

import androidx.room.PrimaryKey
import com.shadowcheck.mobile.core.model.HardwareMetadata

@Entity(tableName = "hardware_metadata")
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: upsert uses REPLACE but macAddress isn’t unique, so inserting the same device will create duplicate rows instead of updating the existing one. Add a unique index (or make macAddress the primary key) so REPLACE works as intended.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At app/src/main/kotlin/com/shadowcheck/mobile/data/database/model/HardwareMetadataEntity.kt, line 7:

<comment>`upsert` uses REPLACE but macAddress isn’t unique, so inserting the same device will create duplicate rows instead of updating the existing one. Add a unique index (or make macAddress the primary key) so REPLACE works as intended.</comment>

<file context>
@@ -0,0 +1,37 @@
+import androidx.room.PrimaryKey
+import com.shadowcheck.mobile.core.model.HardwareMetadata
+
+@Entity(tableName = "hardware_metadata")
+data class HardwareMetadataEntity(
+    @PrimaryKey(autoGenerate = true) val id: Long = 0,
</file context>
Fix with Cubic

@@ -0,0 +1,276 @@
# MVVM Refactoring - COMPLETED ✅
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The doc marks the refactor as completed/100% compliant but later lists multiple screens still using Room.databaseBuilder. Update the completion claim to reflect the remaining work so the status isn’t misleading.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At MVVM_REFACTORING.md, line 1:

<comment>The doc marks the refactor as completed/100% compliant but later lists multiple screens still using Room.databaseBuilder. Update the completion claim to reflect the remaining work so the status isn’t misleading.</comment>

<file context>
@@ -0,0 +1,276 @@
+# MVVM Refactoring - COMPLETED ✅
+
+## Summary
</file context>
Fix with Cubic

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 284aee5103

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

private val MIGRATION_5_6 = object : Migration(5, 6) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE wifi_networks ADD COLUMN sessionId TEXT NOT NULL DEFAULT ''")
database.execSQL("ALTER TABLE ble_devices ADD COLUMN sessionId TEXT NOT NULL DEFAULT ''")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Create missing tables before ALTER TABLE migrations

MIGRATION_5_6 alters ble_devices, but the migration chain in this file never creates that table (and similarly MIGRATION_2_3 alters sensor_readings without a prior create), so upgrading an existing shadowcheck.db can fail with no such table during app startup. This is a blocking upgrade regression for users coming from older schemas, so the new entities need explicit create-table steps (or a different migration strategy) before these ALTER statements run.

Useful? React with 👍 / 👎.

Comment on lines +332 to +336
sensorService = SensorCollectionService(
context = this,
sensorReadingRepository = sensorReadingRepository,
hardwareMetadataRepository = hardwareMetadataRepository,
highPerformanceMode = highPerformanceMode
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve active SensorCollectionService across start intents

onStartCommand always replaces sensorService with a new instance before handling the action, which breaks lifecycle when a second START (or STOP) intent arrives while scanning: startScanning() short-circuits on isScanning, so the new instance is never started, and later stopScanning() stops this new instance instead of the original running one. That leaves the original sensor listeners orphaned and still collecting in the background.

Useful? React with 👍 / 👎.

Comment on lines +518 to +520
runBlocking {
flushPendingWrites()
endCurrentSession()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid blocking service shutdown on main thread

stopScanning() uses runBlocking to flush queued writes and end the session synchronously on the service thread. Because these calls perform multiple repository/database writes, shutdown can block long enough to trigger ANR under heavy scan load. The flush/end operations should run asynchronously instead of blocking the main service lifecycle path.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants