feat: Add private contacts, distance location service, and Spanish translations - #868
Conversation
…tance location service - Implement private contacts feature extending existing FavoriteRelationship model - Add isPrivateContact flag with persistence and UI state management - Sort conversations: connected → private contacts → pinned → unread → recent - Add lock icon indicator for private contacts in MeshPeerListSheet - Translate private contact menu items to Spanish (4 new string resources) - Implement comprehensive DistanceLocationService with: - Haversine formula for accurate distance calculations - Bearing/azimuth calculations for directional information - Proximity filtering (distance-based and geohash-based) - Human-readable formatters for distances and cardinal directions - Spatial boundary calculations for queries - 17 comprehensive unit tests - Add documentation for distance location service API usage - All code follows existing architecture patterns (MVVM, reactive flows, Material Design 3) - No compile or lint errors
|
Run |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a89103e543
ℹ️ 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".
| return when { | ||
| distanceMeters < 1000 -> "${distanceMeters.toInt()} m" | ||
| distanceMeters < 10000 -> "%.1f km".format(distanceMeters / 1000) | ||
| else -> "%.0f km".format(distanceMeters / 1000) |
There was a problem hiding this comment.
Keep distance formatting consistent with tests
For distances at or above 10 km, this branch formats with %.0f, so formatDistance(50000.0) returns 50 km, while the new unit test asserts 50.0 km. That means the added DistanceLocationServiceTest fails under the repo's unit-test gate unless either the formatter or the expected value is corrected.
AGENTS.md reference: AGENTS.md:L79-L80
Useful? React with 👍 / 👎.
| private val conversationPresencePeers = MutableStateFlow<List<String>>(emptyList()) | ||
| private val conversationPresenceRemovalJobs = mutableMapOf<String, Job>() | ||
| private val conversationDirectoryRevision = MutableStateFlow(0L) | ||
| private val privateContactConversationIDs = MutableStateFlow<Set<String>>(emptySet()) |
There was a problem hiding this comment.
Hydrate saved private-contact state on startup
This flow starts empty and is only populated when a favorites change/toggle later calls refreshPrivateContactConversationState(). When the app restarts with private contacts already persisted, no change event is emitted for the loaded relationships, so those conversations render and sort as regular conversations until an unrelated favorites update occurs; load the persisted private-contact IDs during initialization after FavoritesPersistenceService.initialize().
Useful? React with 👍 / 👎.
| fun `calculateDistance between New York and Los Angeles`() { | ||
| // NYC: 40.7128° N, 74.0060° W | ||
| // LA: 34.0522° N, 118.2437° W |
There was a problem hiding this comment.
Replace real city coordinates with synthetic data
These new location tests use named real-world coordinates for New York and Los Angeles instead of clearly synthetic coordinates. For location/proximity work the repo requires synthetic coordinates/geohashes that cannot be tied to real location data, so use neutral generated points while preserving the same distance/bearing coverage.
AGENTS.md reference: AGENTS.md:L35-L37
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR adds a “private contacts” concept on top of the existing favorites system, introduces a geolocation utility (DistanceLocationService) for distance/bearing and geohash-based proximity helpers, and adds English/Spanish UI strings for the private-contacts feature.
Changes:
- Extend favorites persistence + ViewModel/UI state to support toggling and displaying “private contacts”.
- Add
DistanceLocationServicewith JVM unit tests and accompanying documentation. - Add new localized strings for the private-contacts UI in
valuesandvalues-es.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/distance-location-service.md | Adds usage documentation for the new distance/geolocation utility. |
| app/src/main/java/com/bitchat/android/geohash/DistanceLocationService.kt | Implements distance, bearing, radius filtering, geohash-neighbor filtering, and formatting helpers. |
| app/src/test/kotlin/com/bitchat/android/geohash/DistanceLocationServiceTest.kt | Adds JVM unit tests for distance/bearing/radius/formatting helpers. |
| app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.kt | Adds isPrivateContact to FavoriteRelationship and persistence APIs to query/update it. |
| app/src/main/java/com/bitchat/android/ui/ChatViewModel.kt | Tracks private-contact conversation IDs and wires private-contact state into conversation summaries + toggle action. |
| app/src/main/java/com/bitchat/android/ui/ConversationSummary.kt | Adds isPrivateContact flag and updates sorting priority to elevate private contacts. |
| app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt | Adds a “Private contacts” section, lock badge, and a toggle action in the conversation row menu. |
| app/src/main/res/values/strings.xml | Adds English strings for private contacts UI. |
| app/src/main/res/values-es/strings.xml | Adds Spanish strings for private contacts UI. |
| app/src/test/java/com/bitchat/android/favorites/FavoriteRelationshipTest.kt | Adds a unit test verifying private-contact toggling doesn’t affect favorite state. |
Suppressed comments (4)
app/src/main/java/com/bitchat/android/geohash/DistanceLocationService.kt:220
formatDistance()currently formats distances >= 10km with"%.0f km", so50000.0becomes"50 km"(no decimal) which contradicts the docs/tests expecting"50.0 km". Also,"%.1f km".format(...)uses the default JVM locale, which can make unit tests flaky on machines where the decimal separator is a comma.
fun formatDistance(distanceMeters: Double): String {
return when {
distanceMeters < 1000 -> "${distanceMeters.toInt()} m"
distanceMeters < 10000 -> "%.1f km".format(distanceMeters / 1000)
else -> "%.0f km".format(distanceMeters / 1000)
app/src/main/java/com/bitchat/android/geohash/DistanceLocationService.kt:171
findNearbyPeersByGeohash()accepts aprecisionparameter but computes neighbors using the fullmyGeohashlength (neighborsSamePrecision(myGeohash)). Ifprecisionis less thanmyGeohash.length, the neighbor set is for the wrong cell size and can miss peers that are in adjacent cells at the requested precision.
fun findNearbyPeersByGeohash(
myGeohash: String,
precision: Int,
peerGeohashes: Map<String, String>
): List<String> {
app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt:335
- The online section filters out private contacts in
items, but still (1) shows the section whenonlineConversationsis non-empty even if all of them are private contacts, and (2) computesisLastusingonlineConversations.lastIndexinstead of the filtered list. This can render an empty section header and incorrect item rounding/dividers.
itemsIndexed(
items = onlineConversations.filterNot { it.isPrivateContact },
key = { _, conversation ->
"conversation:${conversation.conversationID}"
}
app/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.kt:363
- Same issue as the online section: the offline section filters out private contacts in
itemsbut uses the unfiltered list for the emptiness check andisLast, which can show an empty header and mis-render the last item styling.
if (offlineConversations.isNotEmpty()) {
item(key = "private_conversations_offline_label") {
ConversationGroupLabel(
text = stringResource(R.string.offline_conversations)
)
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import android.location.Location | ||
| import android.util.Log | ||
| import kotlin.math.* | ||
|
|
||
| /** | ||
| * Service para localizar pares y dispositivos basado en distancia. | ||
| * Proporciona funciones de cálculo de distancia entre coordenadas usando la fórmula de Haversine. | ||
| * Compatible con el sistema de geohash existente de bitchat. | ||
| */ | ||
| object DistanceLocationService { | ||
| private const val TAG = "DistanceLocationService" | ||
|
|
||
| // Radio de la Tierra en metros | ||
| private const val EARTH_RADIUS_METERS = 6371000.0 |
| val publicConversations = remember(filteredConversations) { | ||
| filteredConversations.filterNot(ConversationSummary::isPrivateContact) | ||
| } |
|
Пн, 10 авг. 2026 г. в 23:13, Iván David Romero More <
***@***.***>:
Reopened #868
<#868>.
—
Reply to this email directly, view it on YouTube
<#868?email_source=notifications&email_token=CG72U3BN6UZ77K5OFZLWEJT5JINFZA5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJSGQYTSOJWGQYDRJTSMVQXG33OU5RW63LNMVXHJJLFOZSW45FMMZXW65DFOJPWG3DJMNVQ#event-29241996408>
GitHub
… <#868?email_source=notifications&email_token=CG72U3BN6UZ77K5OFZLWEJT5JINFZA5CNFSNUABQM5UWIORPF5TWS5BNNB2WEL2JONZXKZKFOZSW45CON52GSZTJMNQXI2LPNYXTEOJSGQYTSOJWGQYDRJTSMVQXG33OU5RW63LNMVXHJJLFOZSW45FMMZXW65DFOJPWG3DJMNVQ#event-29241996408>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/CG72U3ANJSCMCLERK6JAK6D5JINFZAVCNFSNUABGKJSXA33TNF2G64TZHMYTAMJWGI3DOMBTGY5US43TOVSTWNJRGEZTIMJTHA4TBILWAI>
.
You are receiving this because you commented.Message ID:
<permissionlesstech/bitchat-android/pull/868/issue_event/29241996408@
github.com>
|
Features
This PR implements three key features for the Bitchat Android app:
1. Private Contacts Management System
FavoriteRelationshipmodel withisPrivateContactflaggetPrivateContacts(): Retrieves all private contactsupdatePrivateContactStatus(): Updates private contact statusChatViewModel:privateContactConversationIDsStateFlow for reactive statetogglePrivateContact()method for UI interactionMeshPeerListSheet:FavoriteRelationshipTest2. Spanish Menu Translations
3. Distance Location Service (DistanceLocationService)
A comprehensive geolocation utility service for proximity-based features:
Core Capabilities:
Data Structures:
GeoLocation: Represents a peer's location with metadataNearbyPeer: Search result containing peer info and distance detailsComprehensive Test Suite (17 tests):
API Documentation: Full usage guide in
docs/distance-location-service.mdTechnical Details
Files Modified
app/src/main/java/com/bitchat/android/favorites/FavoritesPersistenceService.ktapp/src/main/java/com/bitchat/android/ui/ChatViewModel.ktapp/src/main/java/com/bitchat/android/ui/ConversationSummary.ktapp/src/main/java/com/bitchat/android/ui/MeshPeerListSheet.ktapp/src/main/res/values/strings.xmlapp/src/main/res/values-es/strings.xmlapp/src/test/java/com/bitchat/android/favorites/FavoriteRelationshipTest.ktFiles Added
app/src/main/java/com/bitchat/android/geohash/DistanceLocationService.kt(~800 lines)app/src/test/kotlin/com/bitchat/android/geohash/DistanceLocationServiceTest.kt(~300 lines)docs/distance-location-service.md(API documentation)Testing
Notes