Commit Graph

29 Commits

Author SHA1 Message Date
torlando-tech
be9e9a512d fix: Resolve ANR during app initialization with 930+ peer identities
Move peer identity restoration to IO dispatcher to prevent blocking
main thread during JSON serialization. Also move interface status
polling to IO dispatcher to fix network status screen lag.

Changes:
- ColumbaApplication: Run restorePeerIdentities on Dispatchers.IO
- InterfaceManagementViewModel: Add injectable ioDispatcher for polling
- ConversationRepository: Remove per-identity logging (930 log calls)
- Update tests to inject test dispatcher for IO operations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 00:50:46 -05:00
torlando-tech
a85a9b0d74 refactor: Convert instrumented tests to Robolectric
Move 7 instrumented tests from androidTest to test directories,
converting them to run with Robolectric for faster execution.

Converted tests:
- SmartPollingPerformanceTest (pure coroutine logic)
- ReticulumServiceBleTest (JSON parsing)
- BleStatusRepositoryTest (mockable dependencies)
- BleConnectionsViewModelTest (ViewModel test)
- BleConnectionStatusScreenTest (Compose UI)
- LocalIdentityDaoTest (Room DAO)
- ContactDaoRelayTest (Room DAO)

Tests NOT converted (require real Android):
- ServiceProcessInitializationTest (multi-process AIDL)
- PythonThreadSafetyTest (Chaquopy runtime)
- BlePermissionRestartTest (permission system)
- ThemeColorGeneratorTest (Android ColorUtils)
- InterfaceDatabaseRaceConditionTest (Chaquopy native libs)

Changes:
- Add Robolectric dependencies to data module
- Create robolectric.properties for data module
- Use @Config(application = Application::class) to avoid
  ColumbaApplication/Chaquopy initialization
- Move shared fixtures to test directories
- Fix detekt issues: use IOException instead of RuntimeException,
  add assertions to exception handlers, remove memory footprint test

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 21:03:35 -05:00
torlando-tech
2c56e98ba5 feat: Add automatic relay fallback and manual retry for failed messages
When a selected relay is offline and message propagation fails, the system
now automatically tries alternative relays before marking the message as
permanently failed. Also adds a manual "Retry" option in the message context
menu for failed messages.

Key changes:
- Python: Modified _on_message_failed() to request alternative relays from
  Kotlin when propagation fails, with tracking to prevent infinite loops
- Kotlin: Added getAlternativeRelay() to PropagationNodeManager to find the
  nearest available relay excluding previously tried ones
- IPC: Added onAlternativeRelayRequested callback and provideAlternativeRelay
  method for Python-Kotlin communication
- UI: Added "Retry" menu item for failed messages and retryFailedMessage()
  in MessagingViewModel

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 00:01:13 -05:00
torlando-tech
afc27d6cdb refactor: Fix detekt/ktlint issues and remove runBlocking antipattern
- Fix TooManyFunctions in PropagationNodeManager by removing restartPeriodicSync
- Fix TooManyFunctions in MessagingViewModel by extracting helpers to top-level
- Fix CyclomaticComplexity in sendMessage by refactoring into smaller methods
- Fix DestructuringDeclarationWithTooManyEntries in MessageDetailScreen
- Remove runBlocking antipattern from PropagationNodeManager StateFlow init
- Apply ktlint formatting fixes across codebase
- Update tests for refactored code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 16:58:06 -05:00
torlando-tech
4ffb7450c9 fix: Settings page shows relay immediately at app launch
Root cause: During PropagationNodeManager singleton construction,
getMyRelay() returned null because localIdentityDao.getActiveIdentitySync()
had no active identity set yet. Additionally, loadSettings() in
SettingsViewModel was overwriting relay state from startRelayMonitor().

Changes:
- Add getAnyMyRelay() query to ContactDao (no identity filter)
- Add getAnyRelay() fallback method to ContactRepository
- Update getInitialRelayInfo() to use fallback when active identity unavailable
- Preserve relay state (currentRelayName, currentRelayHops, autoSelectPropagationNode)
  when loadSettings() updates state

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 15:29:04 -05:00
torlando-tech
c58884718e feat(ui): Add message details screen with delivery tracking
Add "View Details" option to message context menu for sent messages.
New MessageDetailScreen shows:
- Timestamp when message was sent
- Delivery status (pending/sent/delivered/failed)
- Delivery method (opportunistic/direct/propagated)
- Error details for failed messages

Database changes:
- Add deliveryMethod and errorMessage columns to messages table
- Migration 22→23 for schema update

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 12:31:44 -05:00
torlando-tech
3254230025 chore: Fix detekt code quality issues across codebase
- Fix UnsafeCallOnNullableType by capturing nullable values to local vars
- Replace `?: ""` with `.orEmpty()` for idiomatic Kotlin
- Remove redundant suspend modifiers on non-suspending functions
- Fix TestFactories LongParameterList by using Config data classes
- Add justified @Suppress annotations for:
  - TooManyFunctions on ContactDao (expected for DAOs)
  - LargeClass on SettingsViewModel (many UI interactions)
  - CyclomaticComplexMethod on ContactEntity.equals (ByteArray handling)
  - InjectDispatcher on bridge classes (no DI in low-level bridges)
- Remove unused onSelectRelay parameter from MessageDeliveryCard
- Change RuntimeException to error() in PythonReticulumProtocol
- Regenerate detekt baselines for all modules

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 01:38:51 -05:00
torlando-tech
b781076457 test: Add comprehensive unit tests for relay management feature
- Add TestFactories.kt with shared test helpers and valid hex constants
- Add PropagationNodeManagerTest.kt (35 tests) for relay auto-selection
- Add ContactsViewModelTest.kt (~30 tests) for contacts/relay UI
- Expand ContactRepositoryTest.kt with 12 relay management tests
- Fix existing tests for API changes (sendLxmfMessageWithMethod)
- Fix AnnounceStreamViewModelTest and SettingsViewModelTest dependencies

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 01:01:05 -05:00
torlando-tech
0e9cb53582 fix(relay): Start PropagationNodeManager on app launch and fix empty state
- Add PropagationNodeManager.start() call in ColumbaApplication after
  Reticulum initialization to enable relay auto-selection on app startup
- Fix ContactsScreen empty state condition to check for relay contact,
  preventing "No contacts yet" from showing when only a relay exists
- Add debug logging throughout relay selection flow for troubleshooting

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 01:01:05 -05:00
torlando-tech
a11ee755f2 feat(relay): Add propagation node relay management with auto-selection
Implement comprehensive relay (propagation node) support:

- Add PropagationNodeManager for auto-selection of nearest relay by hop count
- Add "Set as My Relay" button on node details for propagation nodes
- Add "MY RELAY" section in contacts screen (separate from pinned)
- Show "(auto)" badge when relay is auto-selected vs manually chosen
- Add "Unset as Your Relay?" confirmation dialog with auto-selection explanation
- Add relay management methods to ContactDao (setAsMyRelay, clearMyRelay, getMyRelay)
- Add isMyRelay field to ContactEntity and EnrichedContact
- Add message delivery settings (default method, retry via relay on fail)
- Add Python layer support for propagation node configuration
- Add instrumented tests for relay DAO operations

The relay system follows Sideband's algorithm: auto-select nearest node,
only switch if new node has fewer or equal hops. Users can manually select
a relay which disables auto-selection until re-enabled.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-12 01:01:05 -05:00
torlando-tech
166bb16300 fix: address 8 critical issues from PR review with TDD
Critical fixes:
- Fix memory leak: RSSI polling now stopped in onCleared()
- Fix silent BLE scan errors: user-friendly error messages added
- Fix double-check locking bug in Python RNode initialization
- Fix interface registration order: start() before Transport register
- Fix race condition: use threading.Event for read loop control
- Fix write retry: implement exponential backoff (0.3s, 1s, 3s)
- Fix BLE write latch race: null check prevents stale callbacks
- Fix MTU request hang: 2-second timeout falls back to discoverServices

Tests added:
- RSSI polling cancellation test
- BLE scan error handling test
- Thread safety tests for read loop
- Write retry exponential backoff tests

Also includes ktlint format auto-fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 13:07:10 -05:00
torlando-tech
9ef62efc73 refactor: simplify AnnounceEntity equals method
Use single return with chained conditions instead of multiple early
returns to reduce cyclomatic complexity. Extract nullable ByteArray
comparison to helper function.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 23:11:09 -05:00
torlando-tech
8e88ad19f1 style: fix detekt and ktlint issues
- Add @Suppress annotations for LargeClass, ReturnCount, CyclomaticComplexMethod, LongMethod
- Extract complex condition to variable in InterfaceManagementScreen
- Suppress UnusedParameter for FrequencySpectrumBar bandwidth parameter
- Format code to pass ktlint checks

Files fixed:
- KotlinRNodeBridge.kt
- RNodeWizardViewModel.kt
- RNodeRegionalPreset.kt
- InterfaceManagementScreen.kt
- InterfaceManagementUtils.kt
- FrequencySlotStep.kt

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
c0e9621638 feat: add input validation for SF, CR, and bandwidth fields
Add real-time validation in updateSpreadingFactor, updateCodingRate,
and updateBandwidth functions to show errors as the user types:
- Spreading Factor: 7-12
- Coding Rate: 5-8
- Bandwidth: 7.8 kHz - 1625 kHz

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
d91287ef66 feat: add automatic RNode reconnection and interface status UI
RNode Auto-Reconnection:
- RNodeCompanionService now triggers reconnection when CompanionDeviceManager
  detects the RNode has reappeared after going out of BLE range
- Add reconnectRNodeInterface() to AIDL interface and ReticulumServiceBinder
- Add thread-safe initialization lock in reticulum_wrapper.py to prevent
  concurrent RNode initialization race conditions
- Use 2-second debounce delay before reconnecting to ensure device stability

Interface Status UI Improvements:
- InterfaceManagementViewModel now polls Reticulum every 3 seconds for
  interface online/offline status
- Update isBleInterface() to include RNode type for proper BLE handling
- Add "Interface Offline" error state to getErrorMessage() for enabled
  interfaces that aren't passing traffic
- Make error badges clickable to show detailed error dialog
- Add InterfaceErrorDialog component for detailed interface issue info
- IdentityScreen: make offline interface rows clickable for troubleshooting

Build & Deploy:
- deploy.sh now supports multiple connected devices, deploying to all of
  them in sequence instead of requiring a single device

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:48 -05:00
torlando-tech
2b46964125 feat: add RNode LoRa interface support via Bluetooth
Implements RNode interface support for LoRa communication via paired
Bluetooth RNode devices. Uses a Kotlin Bridge architecture where Kotlin
handles Bluetooth I/O and Python handles the KISS protocol.

- **KotlinRNodeBridge**: Handles Bluetooth Classic (SPP/RFCOMM) and BLE
  (Nordic UART Service) connections to RNode hardware. Manages connection
  lifecycle, data buffering, and provides read/write APIs to Python.

- **ColumbaRNodeInterface**: Python interface implementing KISS protocol
  for RNode communication. Handles frame escaping, command parsing, radio
  configuration, and integrates with RNS Transport layer.

- **UI Components**: Added RNode configuration fields to InterfaceConfigDialog
  including device name selector, connection mode (Classic/BLE), frequency,
  bandwidth, spreading factor, coding rate, and TX power settings.

- Supports both Bluetooth Classic (UUID: 00001101-0000-1000-8000-00805F9B34FB)
  and BLE via Nordic UART Service (UUID: 6e400001-b5a3-f393-e0a9-e50e24dcca9e)
- Thread-safe circular buffer for BLE packet reassembly
- Automatic device discovery from paired devices list
- Connection state management with callbacks

- Full KISS protocol implementation (FEND/FESC escape sequences)
- RNode detection and firmware version validation
- Radio parameter configuration (frequency, bandwidth, SF, CR, TX power)
- Airtime limiting support (short-term and long-term)
- Required RNS Transport interface attributes for compatibility

- set_rnode_bridge() to receive Kotlin bridge reference
- initialize_rnode_interface() called during bridge setup
- RNode interface registered with RNS.Transport.interfaces

1. **Chaquopy ByteArray conversion**: Raw bytes from Kotlin needed explicit
   `bytes()` conversion in Python due to Chaquopy's jarray handling.

2. **KISS frame format**: Initial detection commands were missing FEND
   delimiters, causing RNode to not respond to detection requests.

3. **RNS Transport compatibility**: Required iteratively adding interface
   attributes (bitrate, rxb, txb, mode, mtu, HW_MTU, FIXED_MTU,
   AUTOCONFIGURE_MTU, announce_rate_target, ifac_size, etc.) and methods
   (sent_announce(), received_announce(), process_held_announces(),
   should_ingress_limit()) to satisfy RNS Transport requirements.

4. **Owner inbound routing**: Changed from owner.inbound() to direct
   RNS.Transport.inbound() calls since owner was ReticulumWrapper, not
   Transport.

Successfully tested bidirectional communication:
- Announces sent and received between Columba and Sideband via LoRa
- Links established with ~1.8s RTT over LoRa
- Messages delivered from Columba to Sideband
- Messages received from Sideband (routing to correct identity required)

- python/rnode_interface.py (NEW): KISS protocol and RNode interface
- reticulum/rnode/KotlinRNodeBridge.kt (NEW): Bluetooth bridge
- python/reticulum_wrapper.py: RNode bridge integration
- ReticulumServiceBinder.kt: Bridge initialization in setupBridges()
- InterfaceConfigDialog.kt: RNode UI configuration fields
- InterfaceManagementViewModel.kt: RNode state management
- ReticulumConfig.kt: RNode data model with targetDeviceName, connectionMode

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-08 21:58:47 -05:00
torlando-tech
9868ec5463 feat: display stamp costs in node details screen
Extract and display stamp cost information from LXMF announces:
- Propagation nodes: show stamp cost with flexibility range and peering cost
- Regular peers: show stamp cost when available

Changes:
- Python: extract stamp costs using LXMF canonical functions
- Add stampCost, stampCostFlexibility, peeringCost to AnnounceEvent model
- Pass stamp costs through PollingManager and ServiceReticulumProtocol
- Add database migration 20->21 for new columns
- Display stamp cost info cards in AnnounceDetailScreen

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 19:23:42 -05:00
torlando-tech
aa5ae22a83 fix: suppress detekt LongParameterList and ThrowsCount warnings
- Add @Suppress("LongParameterList") to buildThemeEntity in CustomThemeRepository
  (entity builder naturally requires all fields)
- Add @Suppress("ThrowsCount") to getLxmfDestination and getLxmfIdentity
  (multiple validation checks require distinct error messages)
- Refactor to use checkNotNull() and require() for cleaner validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:22:37 -05:00
torlando-tech
141a6ed568 refactor: remove duplicate code and add interface corruption resilience
CPD Duplicate Code Removal:
- ThemeEditorViewModel: Extract ColorScheme.toThemeColorSet() extension
- ThemeColorGenerator: Extract shared buildColorScheme() function
- MigrationExporter/Importer: Create CustomThemeEntity.toExport() extension
- AnnounceStreamScreen/SavedPeersScreen: Extract shared PeerCard composable
- CustomThemeRepository: Extract buildThemeEntity() helper function
- IdentityScreen/IdentityQrCodeDialog: Extract IdentityQrCodeContent composable

Interface Repository Resilience:
- Handle corrupted interface configurations gracefully
- Skip invalid interfaces with logging instead of crashing
- Add comprehensive unit tests for corruption scenarios
- Add org.json test dependency for proper JSON parsing in tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 20:27:30 -05:00
Mikel Calvo
898bc6f9d1 Added database indexes to optimize query performance 2025-12-06 02:30:25 +01:00
torlando-tech
1875ae6217 refactor: remove unused name parameters from contact add methods
Follow-up cleanup to remove parameters that are no longer used after
the display name fix. Resolves detekt UnusedParameter warnings.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 14:37:52 -05:00
torlando-tech
5eb2cade34 fix: display name now updates when peer re-announces
Stop copying announce/conversation names to customNickname field,
allowing display names to reflect the latest announce name via the
existing COALESCE fallback.

Includes migration to clear stale auto-populated nicknames for existing
contacts while preserving user-customized ones.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 12:22:07 -05:00
torlando-tech
342e64a524 test: add ContactRepository unit tests for pending identity methods
Add 17 unit tests covering the Sideband import feature's repository layer:
- addPendingContact: announce lookup, immediate resolution, pending creation
- updateContactWithIdentity: pending contact resolution
- updateContactStatus: status transitions (PENDING_IDENTITY, UNRESOLVED)
- getContactsByStatus: querying contacts by status
- getContact: basic retrieval with identity scoping

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:20:51 -05:00
torlando-tech
c36afe7c36 feat: add Sideband contact import support
Enable importing contacts using only a 32-character destination hash
from Sideband's "Copy Address" feature, in addition to full lxma:// URLs.

Key changes:
- Add ContactStatus enum (ACTIVE, PENDING_IDENTITY, UNRESOLVED)
- Make publicKey nullable in ContactEntity for pending contacts
- Add parseIdentityInput() to InputValidator for flexible input parsing
- Add IdentityResolutionManager for background identity resolution
- Check existing announces when adding hash-only contacts
- Update UI with pending/unresolved status indicators
- Add PendingContactBottomSheet for managing unresolved contacts
- Hook announce callbacks for instant resolution when peer announces
- Add recallIdentity() AIDL method to check Reticulum's identity cache
- Database migration 17->18 for new schema

The flow:
1. User pastes 32-char hash -> check announces table for existing identity
2. If found -> add as ACTIVE contact immediately
3. If not found -> add as PENDING_IDENTITY, background resolution kicks in
4. When announce received -> instantly resolve pending contact
5. After 48h timeout -> mark as UNRESOLVED with retry option

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 10:20:51 -05:00
torlando-tech
1d52acb290 fix: add security and robustness improvements to data migration
- Add path traversal validation in extractAttachmentsFromZip() to prevent
  malicious ZIP entries from writing files outside attachments directory
- Wrap main database operations in transaction for atomicity - if any step
  fails, all changes are rolled back to prevent partial imports
- Use IGNORE conflict strategy for message import to preserve existing
  messages and prevent LXMF replay from overwriting timestamps
- Track imported identities and filter dependent data (conversations,
  messages, contacts) to prevent orphaned records if identity import fails
- Add minimum version check for backwards compatibility with old exports

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 23:51:19 -05:00
torlando-tech
062dfe7817 fix: export/import peer_identities and prevent message timestamp overwrite
- Add peer_identities table to migration export/import
  - Added PeerIdentityExport to MigrationData.kt
  - Added batch insert to PeerIdentityDao
  - Bumped migration version to 5
  - Updated tests for new fields

- Fix message ordering after import
  - Prevent LXMF replay from overwriting imported message timestamps
  - Skip inserting messages that already exist in database
  - Root cause: saveMessage() always inserted with REPLACE strategy,
    overwriting original timestamps when LXMF replays buffered messages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 22:58:17 -05:00
torlando-tech
6e24f6677a fix: prevent identity mismatch by using canonical file paths
- Add ensureIdentityFileExists() to IdentityRepository to verify/recover
  identity files from keyData backup before service restart
- Update InterfaceConfigManager to use canonical identity_<hash> paths
  instead of fragile default_identity file
- Add Python safety check to refuse creating new identity when specific
  path was requested but file is missing
- Remove copy-to-default_identity logic from IdentityManagerViewModel
- Add unit tests for identity file recovery scenarios

This fixes the bug where the Python service would silently create a new
identity when the default_identity file was deleted during service restart,
causing the UI to show different identities on different screens.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 22:12:16 -05:00
torlando-tech
d0050e2ce5 feat: add data migration export/import feature
Implements a complete data migration system allowing users to export all app
data from the old app (com.lxmf.messenger) and import it into the new app
(tech.torlando.columba) when changing applicationId for F-Droid publishing.

## Feature Overview

Users can export their data to a `.columba` file (ZIP archive) containing:
- manifest.json: Serialized MigrationBundle with all data
- attachments/: Directory with message attachments

The import process restores all data and restarts the Reticulum service to
apply the imported identities and peer information.

## Data Migrated

| Data Type | Description |
|-----------|-------------|
| Identities | Private keys, display names, destination hashes |
| Conversations | Peer info, last message, unread counts |
| Messages | Full message history with status and timestamps |
| Contacts | Custom nicknames, notes, tags, pinned status |
| Announces | Known peers with public keys for network recognition |
| Settings | Notifications, auto-announce, theme preferences |
| Attachments | Message attachments (images, files, etc.) |

## New Files

- `migration/MigrationData.kt` - Data classes for serialization
- `migration/MigrationExporter.kt` - Export logic with progress callbacks
- `migration/MigrationImporter.kt` - Import logic with validation
- `viewmodel/MigrationViewModel.kt` - UI state management
- `ui/screens/MigrationScreen.kt` - Export/import UI
- `ui/screens/settings/cards/DataMigrationCard.kt` - Settings integration

## Key Implementation Details

### Export Flow
1. Collect all data from Room database
2. Base64-encode binary fields (keys, attachments)
3. Serialize to JSON manifest
4. Create ZIP archive with manifest + attachments
5. Share via FileProvider

### Import Flow
1. Parse ZIP and validate manifest version
2. Import identities (with Reticulum key recovery)
3. Bulk insert conversations, messages, contacts
4. Import announces to database
5. Extract attachments to app storage
6. Apply settings and mark onboarding complete
7. Restart service to apply changes

### Announce Restoration
InterfaceConfigManager Step 9b restores all announce peer identities to
Python Reticulum's known_destinations cache on service restart, enabling
immediate peer recognition without re-announcing.

## DAO Additions

- `AnnounceDao`: getAllAnnouncesSync(), insertAnnounces(), getAnnounceCount()
- `ContactDao`: getAllContactsSync(), insertContacts()
- `ConversationDao`: getAllConversationsList(), insertConversations()
- `MessageDao`: getAllMessagesForIdentity(), insertMessages()

## Dependencies

Added kotlinx-serialization for JSON serialization of migration bundles.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-02 09:25:03 -05:00
torlando-tech
0ce4fba7a3 initial commit 2025-11-30 19:59:14 -05:00