Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
e204978
deps(deps): Bump cameraX from 1.5.2 to 1.5.3
dependabot[bot] Mar 16, 2026
d8bc782
fix: show address for auto-discovered backbone interfaces
torlando-tech Mar 28, 2026
dd05d41
fix: handle inline field=value variables in Micron links
torlando-tech Mar 28, 2026
5b51610
feat: improve NomadNet page request reliability and status reporting
torlando-tech Mar 28, 2026
86f3487
test: update InterfaceInfoTest for address-in-subtitle behavior
torlando-tech Mar 28, 2026
bc630fe
test: add Python tests for format_interface_name fallback paths
torlando-tech Mar 28, 2026
8fa96de
ci: exclude rns_api.py from codecov patch coverage
torlando-tech Mar 28, 2026
700b895
Merge pull request #722 from torlando-tech/fix/interface-name-autodis…
torlando-tech Mar 28, 2026
0925640
Merge pull request #723 from torlando-tech/fix/micron-inline-field-va…
torlando-tech Mar 28, 2026
c980fd5
fix: add epoch guard to stopStatusPolling and @Volatile to job field
torlando-tech Mar 28, 2026
9e67df6
Merge pull request #724 from torlando-tech/feat/nomadnet-request-impr…
torlando-tech Mar 28, 2026
5becb96
feat: add NomadNet file download support
torlando-tech Mar 28, 2026
e7d4851
fix: address Greptile P1 findings in file download
torlando-tech Mar 28, 2026
2ccd8fa
fix: clear browser state on unexpected file response, reset download …
torlando-tech Mar 28, 2026
8eab38c
Merge pull request #725 from torlando-tech/feat/nomadnet-file-downloads
torlando-tech Mar 29, 2026
1f71b78
Add micron parser audit comparing Columba against NomadNet and micron…
claude Mar 14, 2026
3949e72
fix: resolve 7 micron parser discrepancies with NomadNet reference
claude Mar 14, 2026
83ca3dc
fix: resolve detekt NestedBlockDepth in parseInline
claude Mar 14, 2026
fed77e6
docs: mark all audit discrepancies as resolved and verified
torlando-tech Mar 29, 2026
fd106ea
Merge pull request #678 from torlando-tech/claude/audit-columba-parse…
torlando-tech Mar 29, 2026
3803ca8
fix: prevent stale page request from overwriting current page (#677)
torlando-tech Mar 29, 2026
6bb25a8
fix: add epoch guard to catch blocks in fetchPage and submitFormAndNa…
torlando-tech Mar 29, 2026
3cbfafc
fix: add epoch guards to cache-hit paths and protocol-null early exits
torlando-tech Mar 29, 2026
bddd166
fix: prevent announce list scroll jumps when new announces arrive
torlando-tech Mar 29, 2026
8d1cb46
fix: make stableKey() idempotent with eager key array
torlando-tech Mar 29, 2026
0cdda4e
Merge pull request #726 from torlando-tech/fix/677-stale-page-request…
torlando-tech Mar 29, 2026
2e3cdd1
feat: add InterfaceMarker data model and map ViewModel support
torlando-tech Mar 29, 2026
11d1075
feat: render discovered interfaces as map pins with type filtering
torlando-tech Mar 29, 2026
ad2f5f9
feat: persist first-seen timestamp for discovered interfaces
torlando-tech Mar 29, 2026
d0e31b3
fix: align map pin icons with discovery screen icons
torlando-tech Mar 29, 2026
c9c3b5e
fix: use Lucide vector drawables for map pins instead of MDI font
torlando-tech Mar 30, 2026
371a649
fix: match TCP filter chip icon to discovery screen, retry initial load
torlando-tech Mar 30, 2026
8caf263
fix: use exact Material Public globe icon for TCP map pins
torlando-tech Mar 30, 2026
3bb91aa
fix: remove duplicate KDoc comment
torlando-tech Mar 30, 2026
6c4252e
fix: use matching icons for I2P and Yggdrasil in filter chips and pins
torlando-tech Mar 30, 2026
d9f38b0
fix: extract correct incognito icon path from MDI font
torlando-tech Mar 30, 2026
ae61b92
fix: store firstSeen in Unix seconds to match lastHeard format
torlando-tech Mar 30, 2026
96eaefe
docs: explain why only firstSeen is stored in Room
torlando-tech Mar 30, 2026
4436981
fix: address review feedback — test icon, dedup ID, dead code, chip o…
torlando-tech Mar 30, 2026
9f9c84e
fix: use null-separator ID and derived StateFlow for interface markers
torlando-tech Mar 30, 2026
2e0e258
Merge pull request #729 from torlando-tech/feat/interface-map-pins
torlando-tech Mar 30, 2026
0d60ca2
Merge pull request #727 from torlando-tech/fix/announce-list-scroll-jump
torlando-tech Mar 30, 2026
31ed31c
chore: upgrade to Gradle 9.4.1 and AGP 9.1.0 with dependency bumps
torlando-tech Mar 30, 2026
1cb7e2c
fix: use version catalog alias for androidx.test:core
torlando-tech Mar 30, 2026
f3b5037
fix: update stale kotlin version entry in version catalog to 2.3.20
torlando-tech Mar 30, 2026
49f28f9
ci: bump gradle/actions from 5 to 6
dependabot[bot] Mar 30, 2026
3958e44
ci: bump codecov/codecov-action from 5 to 6
dependabot[bot] Mar 30, 2026
4edbdd5
Merge pull request #730 from torlando-tech/chore/gradle9-dependency-u…
torlando-tech Mar 30, 2026
bb5f1b6
deps(deps): Bump the compose group with 2 updates
dependabot[bot] Mar 30, 2026
558f7cb
fix: deduplicate path requests with hasPath guard and remove retry loops
torlando-tech Mar 30, 2026
3a3086e
fix: address review feedback — dead code, stale messages, docstrings
torlando-tech Mar 30, 2026
55fb9fa
fix: use identity hash fallback instead of path request in _on_lxmf_d…
torlando-tech Mar 30, 2026
e248c26
fix: wrap retryResolution in try/catch like sibling methods
torlando-tech Mar 30, 2026
63a485e
Merge pull request #735 from torlando-tech/fix/path-request-dedup
torlando-tech Mar 30, 2026
970a8dd
Merge pull request #731 from torlando-tech/dependabot/github_actions/…
torlando-tech Mar 30, 2026
631d15e
Merge pull request #684 from torlando-tech/dependabot/gradle/compose-…
torlando-tech Mar 30, 2026
d7c4a9f
Merge pull request #685 from torlando-tech/dependabot/gradle/cameraX-…
torlando-tech Mar 30, 2026
6fd1d6e
Merge pull request #732 from torlando-tech/dependabot/github_actions/…
torlando-tech Mar 30, 2026
5b83911
fix: scope MapViewModel announce query to location senders (#734)
torlando-tech Mar 30, 2026
9e483c7
fix: use case-insensitive matching in location sender subquery
torlando-tech Mar 30, 2026
0bfae6d
Merge pull request #737 from torlando-tech/fix/cursor-window-overflow…
torlando-tech Mar 31, 2026
6341cae
Add stats section to README with badges and chart
torlando-tech Mar 31, 2026
02d8171
Center align the Columba icon in README
torlando-tech Mar 31, 2026
ef50d76
feat: foreground service for Doze-resistant location sharing
MatthieuTexier Mar 31, 2026
ffff26c
fix: address adversarial review findings on location service
MatthieuTexier Mar 31, 2026
e617d03
feat: persist and restore location sharing sessions across restarts
MatthieuTexier Mar 31, 2026
65c6154
fix: foreground service for request-only telemetry mode
MatthieuTexier Mar 31, 2026
9fea7b4
fix: resolve leftover conflict markers from cherry-pick
MatthieuTexier Mar 31, 2026
c5c20dd
fix: adversarial review — coordinator sync, restore races, cleanup
MatthieuTexier Mar 31, 2026
736dc6a
fix: dynamic notification text based on active reasons
MatthieuTexier Mar 31, 2026
f290c6d
merge: resolve conflicts with upstream/main
MatthieuTexier Mar 31, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ flags:
ignore:
- "external/**"
- "python/test_*.py"
# Network-dependent Python code requires a live RNS instance, not unit tests
- "python/rns_api.py"
# Service layer code requires instrumented tests, not unit tests
- "app/src/main/java/com/lxmf/messenger/service/**"
- "app/src/main/java/com/lxmf/messenger/reticulum/protocol/**"
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/build-prerelease-apk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
submodules: recursive

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand All @@ -36,7 +36,7 @@ jobs:
python-version: '3.11'

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v5
uses: gradle/actions/wrapper-validation@v6
continue-on-error: true

- name: Grant execute permission for gradlew
Expand Down
18 changes: 9 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
fetch-depth: 1

- name: Validate Gradle wrapper
uses: gradle/actions/wrapper-validation@v5
uses: gradle/actions/wrapper-validation@v6

lint:
name: Code Quality (ktlint + detekt + CPD)
Expand All @@ -40,7 +40,7 @@ jobs:
persist-credentials: false

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down Expand Up @@ -169,7 +169,7 @@ jobs:
persist-credentials: false

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down Expand Up @@ -253,7 +253,7 @@ jobs:
--cov-report=xml:coverage-python.xml

- name: Upload Python coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./python/coverage-python.xml
Expand Down Expand Up @@ -289,7 +289,7 @@ jobs:
persist-credentials: false

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down Expand Up @@ -373,7 +373,7 @@ jobs:
**/build/reports/tests/

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./**/build/reports/jacoco/**/*.xml
Expand Down Expand Up @@ -406,7 +406,7 @@ jobs:
persist-credentials: false

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down Expand Up @@ -467,7 +467,7 @@ jobs:
**/build/reports/tests/

- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
uses: codecov/codecov-action@v6
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./**/build/reports/jacoco/**/*.xml
Expand All @@ -492,7 +492,7 @@ jobs:
persist-credentials: false

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
python-version: '3.11'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
with:
cache-read-only: false

Expand Down
2 changes: 1 addition & 1 deletion LXST-kt
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<img src="./columba-icon.svg" width="200" height="200" alt="alt text" />
<p align="center">
<img src="./columba-icon.svg" width="200" height="200" alt="alt text" />
</p>

# Columba

Expand Down Expand Up @@ -40,3 +42,10 @@ Want to learn more? Visit [Reticulum's documentation](https://reticulum.network/
## Why "Columba"

Columba, latin for "dove," is a [constellation](https://en.wikipedia.org/wiki/Columba_(constellation)) in the southern sky depicting a dove. Doves are commonly a symbol of peace and hope, and have been used as messengers throughout history.

## Stats

![Total Downloads](https://img.shields.io/github/downloads/torlando-tech/columba/total?label=total%20downloads) ![Latest](https://img.shields.io/github/downloads/torlando-tech/columba/latest/total?label=latest%20downloads) ![Pre-release](https://img.shields.io/github/downloads-pre/torlando-tech/columba/latest/total?label=pre-release%20downloads)


[![Star History Chart](https://api.star-history.com/svg?repos=torlando-tech/columba&type=Date)](https://star-history.com/#torlando-tech/columba&Date)
7 changes: 3 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import java.util.Base64

plugins {
id("com.android.application")
kotlin("android")
kotlin("plugin.compose")
kotlin("plugin.serialization")
id("com.google.devtools.ksp")
Expand Down Expand Up @@ -78,7 +77,7 @@ val (versionCodeValue, versionNameValue) = getVersionFromTag()

android {
namespace = "com.lxmf.messenger"
compileSdk = 35
compileSdk = 36

defaultConfig {
applicationId = "com.lxmf.messenger"
Expand Down Expand Up @@ -412,7 +411,7 @@ dependencies {

// Crash Reporting - GlitchTip (Sentry-compatible)
// Phase 4 Task 4.2: Production Observability
implementation("io.sentry:sentry-android:8.29.0")
implementation("io.sentry:sentry-android:8.31.0")

// Performance Monitoring - JankStats for frame monitoring
// Phase 1 Plan 01-03: Frame tracking integration with Sentry
Expand Down Expand Up @@ -453,7 +452,7 @@ dependencies {
testImplementation(libs.compose.test)
testImplementation("androidx.compose.ui:ui-test-manifest")
testImplementation(libs.paging.testing)
testImplementation("androidx.test:core:1.5.0")
testImplementation(libs.test.core)
testImplementation("androidx.test.ext:junit:1.1.5")
testImplementation("org.json:json:20231013") // Real JSON implementation for unit tests
androidTestImplementation(libs.junit.android)
Expand Down
10 changes: 10 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />

<!-- Notification permission (Android 13+) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
Expand Down Expand Up @@ -175,6 +176,15 @@
android:value="Emergency SOS gesture detection via accelerometer" />
</service>

<!-- Location Foreground Service -->
<!-- Keeps the main process alive during Doze so GPS callbacks continue -->
<!-- Only runs when location sharing or telemetry collection is active -->
<service
android:name=".service.LocationForegroundService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />

<!-- Reticulum Background Service -->
<!-- Runs in separate process to allow clean restarts when applying config changes -->
<!-- BLE functionality is now integrated via KotlinBLEBridge (no separate service needed) -->
Expand Down
12 changes: 12 additions & 0 deletions app/src/main/aidl/com/lxmf/messenger/IReticulumService.aidl
Original file line number Diff line number Diff line change
Expand Up @@ -678,6 +678,18 @@ interface IReticulumService {
*/
oneway void cancelNomadnetPageRequest();

/**
* Get current NomadNet file download progress.
* @return Float between 0.0 and 1.0 if download active, -1.0 if idle
*/
float getNomadnetDownloadProgress();

/**
* Get current NomadNet request phase status (e.g., "Looking up path...", "Connecting (3 hops)...").
* @return Status string, empty if idle
*/
String getNomadnetRequestStatus();

/**
* Identify ourselves on an existing NomadNet link (Strangler Fig -> rns_api.py).
* @param destHash Destination hash bytes (16 bytes)
Expand Down
10 changes: 9 additions & 1 deletion app/src/main/java/com/lxmf/messenger/ColumbaApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.lxmf.messenger.reticulum.model.ReticulumConfig
import com.lxmf.messenger.reticulum.protocol.ReticulumProtocol
import com.lxmf.messenger.reticulum.protocol.ServiceReticulumProtocol
import com.lxmf.messenger.service.IdentityResolutionManager
import com.lxmf.messenger.service.LocationSharingManager
import com.lxmf.messenger.service.MessageCollector
import com.lxmf.messenger.service.PropagationNodeManager
import com.lxmf.messenger.service.SosActiveTracker
Expand Down Expand Up @@ -100,6 +101,9 @@ class ColumbaApplication : Application() {
@Inject
lateinit var receivedLocationDao: com.lxmf.messenger.data.db.dao.ReceivedLocationDao

@Inject
lateinit var locationSharingManager: LocationSharingManager

// Application-level coroutine scope for app-wide operations
// Uses Dispatchers.Default for background initialization (no main-thread work needed)
// SupervisorJob ensures failures don't crash the entire app
Expand Down Expand Up @@ -284,7 +288,8 @@ class ColumbaApplication : Application() {
// (collector address + send/request toggles) is available even if
// startup exits early while service is INITIALIZING/RESTARTING.
telemetryCollectorManager.start()
android.util.Log.d("ColumbaApplication", "TelemetryCollectorManager started early after bind")
locationSharingManager.restoreIfActive()
android.util.Log.d("ColumbaApplication", "TelemetryCollectorManager + LocationSharingManager started early after bind")

// Check if service is already initialized (handle service process surviving app restart)
// Use timeout to prevent ANR if service is slow
Expand Down Expand Up @@ -325,6 +330,7 @@ class ColumbaApplication : Application() {
identityResolutionManager.start(applicationScope)
propagationNodeManager.start()
telemetryCollectorManager.start()
locationSharingManager.restoreIfActive()
android.util.Log.d(
"ColumbaApplication",
"MessageCollector, AutoAnnounceManager, IdentityResolutionManager, PropagationNodeManager, TelemetryCollectorManager started",
Expand Down Expand Up @@ -443,6 +449,7 @@ class ColumbaApplication : Application() {
identityResolutionManager.start(applicationScope)
propagationNodeManager.start()
telemetryCollectorManager.start()
locationSharingManager.restoreIfActive()
android.util.Log.d(
"ColumbaApplication",
"MessageCollector, AutoAnnounceManager, IdentityResolutionManager, PropagationNodeManager, TelemetryCollectorManager started",
Expand Down Expand Up @@ -638,6 +645,7 @@ class ColumbaApplication : Application() {
identityResolutionManager.start(applicationScope)
propagationNodeManager.start()
telemetryCollectorManager.start()
locationSharingManager.restoreIfActive()
android.util.Log.d(
"ColumbaApplication",
"initializeReticulumService: MessageCollector, AutoAnnounceManager, IdentityResolutionManager, PropagationNodeManager, TelemetryCollectorManager started",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ class SettingsRepository
// SOS audio recording
val SOS_AUDIO_ENABLED = booleanPreferencesKey("sos_audio_enabled")
val SOS_AUDIO_DURATION_SECONDS = intPreferencesKey("sos_audio_duration_seconds")

// Persisted location sharing sessions (JSON array, survives restart)
val LOCATION_SHARING_SESSIONS = stringPreferencesKey("location_sharing_sessions")
}

// Cross-process SharedPreferences for service communication
Expand Down Expand Up @@ -2206,4 +2209,21 @@ class SettingsRepository
preferences[PreferencesKeys.SOS_PILL_OFFSET_Y] = y
}
}

// ========== Location Sharing Session Persistence ==========

suspend fun saveLocationSharingSessions(sessionsJson: String) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.LOCATION_SHARING_SESSIONS] = sessionsJson
}
}

suspend fun getLocationSharingSessions(): String? =
context.dataStore.data.first()[PreferencesKeys.LOCATION_SHARING_SESSIONS]

suspend fun clearLocationSharingSessions() {
context.dataStore.edit { preferences ->
preferences.remove(PreferencesKeys.LOCATION_SHARING_SESSIONS)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2874,10 +2874,22 @@ class ServiceReticulumProtocol(

val result = org.json.JSONObject(resultJson)
if (result.optBoolean("success", false)) {
NomadnetPageResult(
content = result.getString("content"),
path = result.getString("path"),
)
val type = result.optString("type", "page")
if (type == "file") {
NomadnetPageResult(
content = "",
path = result.getString("path"),
type = "file",
filePath = result.getString("file_path"),
fileName = result.getString("file_name"),
fileSize = result.getLong("file_size"),
)
} else {
NomadnetPageResult(
content = result.getString("content"),
path = result.getString("path"),
)
}
} else {
throw RuntimeException(result.optString("error", "Unknown error"))
}
Expand All @@ -2894,6 +2906,28 @@ class ServiceReticulumProtocol(
}
}

suspend fun getNomadnetRequestStatus(): String =
kotlinx.coroutines.withContext(Dispatchers.IO) {
try {
this@ServiceReticulumProtocol.service?.nomadnetRequestStatus ?: ""
} catch (
@Suppress("SwallowedException") e: Exception,
) {
""
}
}

suspend fun getNomadnetDownloadProgress(): Float =
kotlinx.coroutines.withContext(Dispatchers.IO) {
try {
this@ServiceReticulumProtocol.service?.nomadnetDownloadProgress ?: -1f
} catch (
@Suppress("SwallowedException") e: Exception,
) {
-1f
}
}

/**
* Identify ourselves on an existing NomadNet link.
* @return Result<Boolean> where Boolean = alreadyIdentified
Expand All @@ -2917,6 +2951,10 @@ class ServiceReticulumProtocol(
data class NomadnetPageResult(
val content: String,
val path: String,
val type: String = "page",
val filePath: String? = null,
val fileName: String? = null,
val fileSize: Long = 0L,
)

// Helper extension functions
Expand Down
Loading
Loading