Android's app sandboxing prevents Unix domain sockets from working
between different apps. Added `shared_instance_type = tcp` to the
RNS config so Columba can properly connect to Sideband's shared
instance via TCP on port 37428.
Additional changes:
- Replace one-way button with bidirectional toggle for instance mode
- Show banner when using own instance (so user can toggle back)
- Add restart dialog when switching instance modes
- Disable Service Control card when using shared instance
- Disable BLE Connections card when using shared instance
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add backend infrastructure to detect and connect to shared Reticulum
instances (e.g., from Sideband running on the same device).
Changes:
- ReticulumConfig: Add preferOwnInstance field
- reticulum_wrapper.py: Add _check_shared_instance_available() to detect
TCP shared instances on port 37428, update _create_config_file() to
support shared instance mode, return is_shared_instance in initialize()
- ServiceReticulumProtocol: Parse is_shared_instance from result and
save to SettingsRepository
- PythonWrapperManager: Parse is_shared_instance and pass to callback
- ReticulumServiceBinder: Include is_shared_instance in callback JSON
- InterfaceConfigManager: Pass preferOwnInstance to ReticulumConfig
- ColumbaApplication: Load and pass preferOwnInstance preference
Flow:
1. User preference preferOwnInstance loaded from SettingsRepository
2. If false, Python checks for TCP connection to 127.0.0.1:37428
3. If shared instance found, config uses share_instance=yes mode
4. is_shared_instance result saved to SettingsRepository
5. UI reacts via SettingsViewModel reading from repository
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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>
- 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>
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>
Add optional network_name and passphrase fields to TCP interface
configuration, enabling Reticulum's Interface Access Code (IFAC)
cryptographic authentication.
Changes:
- Add networkName and passphrase fields to TCPClient data class
- Update InterfaceRepository JSON serialization/deserialization
- Add UI fields in Advanced Options with info text explaining IFAC
- Passphrase field hidden by default with visibility toggle
- Pass IFAC params from Kotlin to Python via JSON bridge
- Generate network_name and passphrase in config file
IFAC allows interfaces with matching network_name and passphrase to
communicate securely by signing and verifying packets using keys
derived from these shared credentials.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>