mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-08-29 07:16:08 +00:00
Gate nearby notes behind tap-to-reveal consent (#771)
* Add nearby notes tap-to-reveal consent * Stop nearby notes while app is backgrounded
This commit is contained in:
parent
92d07b22fa
commit
d615fc9cc0
@ -94,6 +94,8 @@ class LocationNotesManager private constructor() {
|
|||||||
|
|
||||||
// Coroutine scope for background operations
|
// Coroutine scope for background operations
|
||||||
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
private var subscribeRetryJob: Job? = null
|
||||||
|
private var initialLoadJob: Job? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize dependencies
|
* Initialize dependencies
|
||||||
@ -289,6 +291,11 @@ class LocationNotesManager private constructor() {
|
|||||||
* Subscribe to location notes for current geohash
|
* Subscribe to location notes for current geohash
|
||||||
*/
|
*/
|
||||||
private fun subscribeAll() {
|
private fun subscribeAll() {
|
||||||
|
subscribeRetryJob?.cancel()
|
||||||
|
subscribeRetryJob = null
|
||||||
|
initialLoadJob?.cancel()
|
||||||
|
initialLoadJob = null
|
||||||
|
|
||||||
val currentGeohash = _geohash.value
|
val currentGeohash = _geohash.value
|
||||||
if (currentGeohash == null) {
|
if (currentGeohash == null) {
|
||||||
Log.w(TAG, "Cannot subscribe - no geohash set")
|
Log.w(TAG, "Cannot subscribe - no geohash set")
|
||||||
@ -301,7 +308,7 @@ class LocationNotesManager private constructor() {
|
|||||||
Log.e(TAG, "Cannot subscribe - subscribe function not initialized; will retry shortly")
|
Log.e(TAG, "Cannot subscribe - subscribe function not initialized; will retry shortly")
|
||||||
_state.value = State.LOADING
|
_state.value = State.LOADING
|
||||||
// Retry a few times in case initialization is racing the sheet open
|
// Retry a few times in case initialization is racing the sheet open
|
||||||
scope.launch {
|
subscribeRetryJob = scope.launch {
|
||||||
var attempts = 0
|
var attempts = 0
|
||||||
while (attempts < 10 && subscribeFunc == null) {
|
while (attempts < 10 && subscribeFunc == null) {
|
||||||
delay(300)
|
delay(300)
|
||||||
@ -342,9 +349,9 @@ class LocationNotesManager private constructor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark initial load complete after brief delay to allow relay responses
|
// Mark initial load complete after brief delay to allow relay responses
|
||||||
scope.launch {
|
initialLoadJob = scope.launch {
|
||||||
delay(2000) // Wait 2 seconds for initial batch
|
delay(2000) // Wait 2 seconds for initial batch
|
||||||
if (!_initialLoadComplete.value!!) {
|
if (_geohash.value == currentGeohash && !_initialLoadComplete.value) {
|
||||||
_initialLoadComplete.value = true
|
_initialLoadComplete.value = true
|
||||||
_state.value = State.READY
|
_state.value = State.READY
|
||||||
Log.d(TAG, "Initial load complete for geohash: $currentGeohash (${noteIDs.size} notes)")
|
Log.d(TAG, "Initial load complete for geohash: $currentGeohash (${noteIDs.size} notes)")
|
||||||
@ -441,6 +448,11 @@ class LocationNotesManager private constructor() {
|
|||||||
* Cancel subscription and clear state
|
* Cancel subscription and clear state
|
||||||
*/
|
*/
|
||||||
fun cancel() {
|
fun cancel() {
|
||||||
|
subscribeRetryJob?.cancel()
|
||||||
|
subscribeRetryJob = null
|
||||||
|
initialLoadJob?.cancel()
|
||||||
|
initialLoadJob = null
|
||||||
|
|
||||||
if (subscriptionIDs.isNotEmpty()) {
|
if (subscriptionIDs.isNotEmpty()) {
|
||||||
subscriptionIDs.values.forEach { subId ->
|
subscriptionIDs.values.forEach { subId ->
|
||||||
try {
|
try {
|
||||||
@ -455,15 +467,24 @@ class LocationNotesManager private constructor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cleanup resources
|
* End the nearby-notes session and discard location-correlated UI state.
|
||||||
|
* Unlike [cancel], this also clears the target so a later activation can
|
||||||
|
* safely subscribe to the same building geohash again.
|
||||||
*/
|
*/
|
||||||
fun cleanup() {
|
fun stop() {
|
||||||
cancel()
|
cancel()
|
||||||
scope.cancel()
|
|
||||||
_notes.value = emptyList()
|
_notes.value = emptyList()
|
||||||
noteIDs.clear()
|
noteIDs.clear()
|
||||||
_geohash.value = null
|
_geohash.value = null
|
||||||
_initialLoadComplete.value = false
|
_initialLoadComplete.value = false
|
||||||
_errorMessage.value = null
|
_errorMessage.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cleanup resources
|
||||||
|
*/
|
||||||
|
fun cleanup() {
|
||||||
|
stop()
|
||||||
|
scope.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,130 @@
|
|||||||
|
package com.bitchat.android.nostr
|
||||||
|
|
||||||
|
import androidx.annotation.MainThread
|
||||||
|
import com.bitchat.android.geohash.GeohashChannel
|
||||||
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Session-scoped consent gate for nearby location notes.
|
||||||
|
*
|
||||||
|
* Merely rendering the mesh timeline must not open a building-precision Nostr
|
||||||
|
* subscription. A subscription is eligible only after an explicit reveal and
|
||||||
|
* while the app is foregrounded and at least one nearby-notes surface is active.
|
||||||
|
*/
|
||||||
|
@MainThread
|
||||||
|
class NearbyNotesController internal constructor(
|
||||||
|
private val subscribe: (String) -> Unit,
|
||||||
|
private val unsubscribe: () -> Unit,
|
||||||
|
) {
|
||||||
|
private val _revealed = MutableStateFlow(false)
|
||||||
|
val revealed: StateFlow<Boolean> = _revealed.asStateFlow()
|
||||||
|
|
||||||
|
private var activeHolders = 0
|
||||||
|
private var locationEnabled = false
|
||||||
|
private var locationAuthorized = false
|
||||||
|
private var appForeground = false
|
||||||
|
private var buildingGeohash: String? = null
|
||||||
|
private var subscribedGeohash: String? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unlocks nearby notes for this process session. Deactivation deliberately
|
||||||
|
* does not reset consent, matching the iOS privacy model.
|
||||||
|
*/
|
||||||
|
fun reveal() {
|
||||||
|
if (_revealed.value) return
|
||||||
|
_revealed.value = true
|
||||||
|
reconcileSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Holds the subscription while a nearby-notes surface is visible. */
|
||||||
|
fun activate() {
|
||||||
|
activeHolders += 1
|
||||||
|
reconcileSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Releases a matching [activate] hold and unsubscribes after the last one. */
|
||||||
|
fun deactivate() {
|
||||||
|
activeHolders = (activeHolders - 1).coerceAtLeast(0)
|
||||||
|
reconcileSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Closes the live subscription whenever the process leaves the foreground. */
|
||||||
|
fun updateAppForeground(isForeground: Boolean) {
|
||||||
|
appForeground = isForeground
|
||||||
|
reconcileSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the privacy-sensitive inputs independently of view activation.
|
||||||
|
* Permission revocation, location disable, or loss of the building cell
|
||||||
|
* immediately closes any live subscription.
|
||||||
|
*/
|
||||||
|
fun updateAvailability(
|
||||||
|
locationEnabled: Boolean,
|
||||||
|
locationAuthorized: Boolean,
|
||||||
|
buildingGeohash: String?,
|
||||||
|
) {
|
||||||
|
this.locationEnabled = locationEnabled
|
||||||
|
this.locationAuthorized = locationAuthorized
|
||||||
|
this.buildingGeohash = buildingGeohash
|
||||||
|
?.trim()
|
||||||
|
?.lowercase()
|
||||||
|
?.takeIf { it.isNotEmpty() }
|
||||||
|
reconcileSubscription()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun offersRevealHint(): Boolean =
|
||||||
|
!_revealed.value &&
|
||||||
|
locationEnabled &&
|
||||||
|
locationAuthorized &&
|
||||||
|
buildingGeohash != null
|
||||||
|
|
||||||
|
private fun reconcileSubscription() {
|
||||||
|
val target = buildingGeohash.takeIf {
|
||||||
|
activeHolders > 0 &&
|
||||||
|
appForeground &&
|
||||||
|
_revealed.value &&
|
||||||
|
locationEnabled &&
|
||||||
|
locationAuthorized
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subscribedGeohash != null && subscribedGeohash != target) {
|
||||||
|
unsubscribe()
|
||||||
|
subscribedGeohash = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target != null && subscribedGeohash == null) {
|
||||||
|
subscribe(target)
|
||||||
|
subscribedGeohash = target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val shared: NearbyNotesController by lazy {
|
||||||
|
val manager = LocationNotesManager.getInstance()
|
||||||
|
NearbyNotesController(
|
||||||
|
subscribe = manager::setGeohash,
|
||||||
|
unsubscribe = manager::stop,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Building precision is location-notes precision and remains private before a
|
||||||
|
* reveal. Explicit bookmarks remain eligible because saving one is itself an
|
||||||
|
* intentional location act.
|
||||||
|
*/
|
||||||
|
internal fun geohashesForSampling(
|
||||||
|
availableChannels: List<GeohashChannel>,
|
||||||
|
bookmarks: Collection<String>,
|
||||||
|
notesRevealed: Boolean,
|
||||||
|
): List<String> = buildSet {
|
||||||
|
availableChannels
|
||||||
|
.filter { notesRevealed || it.level != GeohashChannelLevel.BUILDING }
|
||||||
|
.mapTo(this) { it.geohash }
|
||||||
|
addAll(bookmarks)
|
||||||
|
}.toList()
|
||||||
@ -12,19 +12,35 @@ import androidx.compose.runtime.*
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ArrowDownward
|
import androidx.compose.material.icons.filled.ArrowDownward
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.semantics.contentDescription
|
||||||
|
import androidx.compose.ui.semantics.semantics
|
||||||
|
import androidx.compose.ui.semantics.clearAndSetSemantics
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import androidx.lifecycle.DefaultLifecycleObserver
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.bitchat.android.R
|
||||||
|
import com.bitchat.android.geohash.ChannelID
|
||||||
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
import com.bitchat.android.model.BitchatMessage
|
import com.bitchat.android.model.BitchatMessage
|
||||||
|
import com.bitchat.android.nostr.LocationNotesManager
|
||||||
|
import com.bitchat.android.nostr.NearbyNotesController
|
||||||
import com.bitchat.android.ui.media.FullScreenImageViewer
|
import com.bitchat.android.ui.media.FullScreenImageViewer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -86,6 +102,67 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
|||||||
|
|
||||||
// Get location channel info for timeline switching
|
// Get location channel info for timeline switching
|
||||||
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
val selectedLocationChannel by viewModel.selectedLocationChannel.collectAsStateWithLifecycle()
|
||||||
|
val context = LocalContext.current
|
||||||
|
val locationManager = remember { LocationChannelManager.getInstance(context) }
|
||||||
|
val nearbyNotesController = remember { NearbyNotesController.shared }
|
||||||
|
val nearbyNotesRevealed by nearbyNotesController.revealed.collectAsStateWithLifecycle()
|
||||||
|
val locationPermissionState by locationManager.permissionState.collectAsStateWithLifecycle()
|
||||||
|
val locationEnabled by locationManager.effectiveLocationEnabled.collectAsStateWithLifecycle(false)
|
||||||
|
val availableLocationChannels by locationManager.availableChannels.collectAsStateWithLifecycle()
|
||||||
|
val nearbyNotes by remember { LocationNotesManager.getInstance() }
|
||||||
|
.notes
|
||||||
|
.collectAsStateWithLifecycle()
|
||||||
|
val buildingGeohash = availableLocationChannels
|
||||||
|
.firstOrNull { it.level == GeohashChannelLevel.BUILDING }
|
||||||
|
?.geohash
|
||||||
|
val isMeshTimeline =
|
||||||
|
currentChannel == null &&
|
||||||
|
selectedLocationChannel is ChannelID.Mesh &&
|
||||||
|
selectedPrivatePeer == null &&
|
||||||
|
privateChatSheetPeer == null
|
||||||
|
|
||||||
|
val processLifecycleOwner = remember { ProcessLifecycleOwner.get() }
|
||||||
|
DisposableEffect(processLifecycleOwner, nearbyNotesController) {
|
||||||
|
val lifecycle = processLifecycleOwner.lifecycle
|
||||||
|
val observer = object : DefaultLifecycleObserver {
|
||||||
|
override fun onStart(owner: LifecycleOwner) {
|
||||||
|
nearbyNotesController.updateAppForeground(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop(owner: LifecycleOwner) {
|
||||||
|
nearbyNotesController.updateAppForeground(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle.addObserver(observer)
|
||||||
|
nearbyNotesController.updateAppForeground(
|
||||||
|
lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED),
|
||||||
|
)
|
||||||
|
|
||||||
|
onDispose {
|
||||||
|
lifecycle.removeObserver(observer)
|
||||||
|
nearbyNotesController.updateAppForeground(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(
|
||||||
|
isMeshTimeline,
|
||||||
|
locationEnabled,
|
||||||
|
locationPermissionState,
|
||||||
|
buildingGeohash,
|
||||||
|
nearbyNotesController,
|
||||||
|
) {
|
||||||
|
nearbyNotesController.updateAvailability(
|
||||||
|
locationEnabled = locationEnabled,
|
||||||
|
locationAuthorized =
|
||||||
|
locationPermissionState == LocationChannelManager.PermissionState.AUTHORIZED,
|
||||||
|
buildingGeohash = buildingGeohash,
|
||||||
|
)
|
||||||
|
if (isMeshTimeline) nearbyNotesController.activate()
|
||||||
|
onDispose {
|
||||||
|
if (isMeshTimeline) nearbyNotesController.deactivate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Determine what messages to show based on current context (unified timelines)
|
// Determine what messages to show based on current context (unified timelines)
|
||||||
// Legacy private chat timeline removed - private chats now exclusively use PrivateChatSheet
|
// Legacy private chat timeline removed - private chats now exclusively use PrivateChatSheet
|
||||||
@ -131,58 +208,91 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Messages area - takes up available space, will compress when keyboard appears
|
// Messages area - takes up available space, will compress when keyboard appears
|
||||||
MessagesList(
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
messages = displayMessages,
|
if (isMeshTimeline && nearbyNotesRevealed && nearbyNotes.isNotEmpty()) {
|
||||||
currentUserNickname = nickname,
|
NearbyNotesStrip(
|
||||||
meshService = viewModel.meshServiceFacade,
|
noteCount = nearbyNotes.size,
|
||||||
modifier = Modifier.weight(1f),
|
onClick = { showLocationNotesSheet = true },
|
||||||
forceScrollToBottom = forceScrollToBottom,
|
|
||||||
onScrolledUpChanged = { isUp -> isScrolledUp = isUp },
|
|
||||||
onNicknameClick = { fullSenderName ->
|
|
||||||
// Single click - mention user in text input
|
|
||||||
val currentText = messageText.text
|
|
||||||
|
|
||||||
// Extract base nickname and hash suffix from full sender name
|
|
||||||
val (baseName, hashSuffix) = splitSuffix(fullSenderName)
|
|
||||||
|
|
||||||
// Check if we're in a geohash channel to include hash suffix
|
|
||||||
val selectedLocationChannel = viewModel.selectedLocationChannel.value
|
|
||||||
val mentionText = if (selectedLocationChannel is com.bitchat.android.geohash.ChannelID.Location && hashSuffix.isNotEmpty()) {
|
|
||||||
// In geohash chat - include the hash suffix from the full display name
|
|
||||||
"@$baseName$hashSuffix"
|
|
||||||
} else {
|
|
||||||
// Regular chat - just the base nickname
|
|
||||||
"@$baseName"
|
|
||||||
}
|
|
||||||
|
|
||||||
val newText = when {
|
|
||||||
currentText.isEmpty() -> "$mentionText "
|
|
||||||
currentText.endsWith(" ") -> "$currentText$mentionText "
|
|
||||||
else -> "$currentText $mentionText "
|
|
||||||
}
|
|
||||||
|
|
||||||
messageText = TextFieldValue(
|
|
||||||
text = newText,
|
|
||||||
selection = TextRange(newText.length)
|
|
||||||
)
|
)
|
||||||
},
|
|
||||||
onMessageLongPress = { message ->
|
|
||||||
// Message long press - open user action sheet with message context
|
|
||||||
// Extract base nickname from message sender (contains all necessary info)
|
|
||||||
val (baseName, _) = splitSuffix(message.sender)
|
|
||||||
selectedUserForSheet = baseName
|
|
||||||
selectedMessageForSheet = message
|
|
||||||
showUserSheet = true
|
|
||||||
},
|
|
||||||
onCancelTransfer = { msg ->
|
|
||||||
viewModel.cancelMediaSend(msg.id)
|
|
||||||
},
|
|
||||||
onImageClick = { currentPath, allImagePaths, initialIndex ->
|
|
||||||
viewerImagePaths = allImagePaths
|
|
||||||
initialViewerIndex = initialIndex
|
|
||||||
showFullScreenImageViewer = true
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
MessagesList(
|
||||||
|
messages = displayMessages,
|
||||||
|
currentUserNickname = nickname,
|
||||||
|
meshService = viewModel.meshServiceFacade,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
forceScrollToBottom = forceScrollToBottom,
|
||||||
|
onScrolledUpChanged = { isUp -> isScrolledUp = isUp },
|
||||||
|
onNicknameClick = { fullSenderName ->
|
||||||
|
// Single click - mention user in text input
|
||||||
|
val currentText = messageText.text
|
||||||
|
|
||||||
|
// Extract base nickname and hash suffix from full sender name
|
||||||
|
val (baseName, hashSuffix) = splitSuffix(fullSenderName)
|
||||||
|
|
||||||
|
// Check if we're in a geohash channel to include hash suffix
|
||||||
|
val selectedLocationChannel = viewModel.selectedLocationChannel.value
|
||||||
|
val mentionText = if (
|
||||||
|
selectedLocationChannel is ChannelID.Location &&
|
||||||
|
hashSuffix.isNotEmpty()
|
||||||
|
) {
|
||||||
|
// In geohash chat - include the hash suffix from the full display name
|
||||||
|
"@$baseName$hashSuffix"
|
||||||
|
} else {
|
||||||
|
// Regular chat - just the base nickname
|
||||||
|
"@$baseName"
|
||||||
|
}
|
||||||
|
|
||||||
|
val newText = when {
|
||||||
|
currentText.isEmpty() -> "$mentionText "
|
||||||
|
currentText.endsWith(" ") -> "$currentText$mentionText "
|
||||||
|
else -> "$currentText $mentionText "
|
||||||
|
}
|
||||||
|
|
||||||
|
messageText = TextFieldValue(
|
||||||
|
text = newText,
|
||||||
|
selection = TextRange(newText.length),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onMessageLongPress = { message ->
|
||||||
|
// Message long press - open user action sheet with message context
|
||||||
|
// Extract base nickname from message sender (contains all necessary info)
|
||||||
|
val (baseName, _) = splitSuffix(message.sender)
|
||||||
|
selectedUserForSheet = baseName
|
||||||
|
selectedMessageForSheet = message
|
||||||
|
showUserSheet = true
|
||||||
|
},
|
||||||
|
onCancelTransfer = { msg ->
|
||||||
|
viewModel.cancelMediaSend(msg.id)
|
||||||
|
},
|
||||||
|
onImageClick = { currentPath, allImagePaths, initialIndex ->
|
||||||
|
viewerImagePaths = allImagePaths
|
||||||
|
initialViewerIndex = initialIndex
|
||||||
|
showFullScreenImageViewer = true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
displayMessages.isEmpty() &&
|
||||||
|
isMeshTimeline &&
|
||||||
|
!nearbyNotesRevealed &&
|
||||||
|
locationEnabled &&
|
||||||
|
locationPermissionState ==
|
||||||
|
LocationChannelManager.PermissionState.AUTHORIZED &&
|
||||||
|
buildingGeohash != null
|
||||||
|
) {
|
||||||
|
NearbyNotesRevealHint(
|
||||||
|
onClick = nearbyNotesController::reveal,
|
||||||
|
modifier = Modifier.align(Alignment.Center),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Input area - stays at bottom
|
// Input area - stays at bottom
|
||||||
// Bridge file share from lower-level input to ViewModel
|
// Bridge file share from lower-level input to ViewModel
|
||||||
androidx.compose.runtime.LaunchedEffect(Unit) {
|
androidx.compose.runtime.LaunchedEffect(Unit) {
|
||||||
@ -253,7 +363,10 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
|||||||
onShowAppInfo = { viewModel.showAppInfo() },
|
onShowAppInfo = { viewModel.showAppInfo() },
|
||||||
onPanicClear = { viewModel.panicClearAllData() },
|
onPanicClear = { viewModel.panicClearAllData() },
|
||||||
onLocationChannelsClick = { showLocationChannelsSheet = true },
|
onLocationChannelsClick = { showLocationChannelsSheet = true },
|
||||||
onLocationNotesClick = { showLocationNotesSheet = true }
|
onLocationNotesClick = {
|
||||||
|
nearbyNotesController.reveal()
|
||||||
|
showLocationNotesSheet = true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// Divider under header - positioned after status bar + header height
|
// Divider under header - positioned after status bar + header height
|
||||||
@ -374,6 +487,68 @@ fun ChatScreen(viewModel: ChatViewModel) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NearbyNotesRevealHint(
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val actionLabel = stringResource(R.string.nearby_notes_reveal)
|
||||||
|
TextButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 48.dp)
|
||||||
|
.padding(horizontal = 24.dp)
|
||||||
|
.semantics { contentDescription = actionLabel },
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "📍 $actionLabel",
|
||||||
|
modifier = Modifier.clearAndSetSemantics { },
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun NearbyNotesStrip(
|
||||||
|
noteCount: Int,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.fillMaxWidth(),
|
||||||
|
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 48.dp)
|
||||||
|
.padding(horizontal = 12.dp, vertical = 7.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "📍 " + if (noteCount == 1) {
|
||||||
|
stringResource(R.string.nearby_notes_one)
|
||||||
|
} else {
|
||||||
|
stringResource(R.string.nearby_notes_many, noteCount)
|
||||||
|
},
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontFamily = FontFamily.Monospace,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "›",
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = 18.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatInputSection(
|
fun ChatInputSection(
|
||||||
messageText: TextFieldValue,
|
messageText: TextFieldValue,
|
||||||
|
|||||||
@ -34,6 +34,8 @@ import com.bitchat.android.geohash.GeohashChannel
|
|||||||
import com.bitchat.android.geohash.GeohashChannelLevel
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
import com.bitchat.android.geohash.LocationChannelManager
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
import com.bitchat.android.geohash.GeohashBookmarksStore
|
import com.bitchat.android.geohash.GeohashBookmarksStore
|
||||||
|
import com.bitchat.android.nostr.NearbyNotesController
|
||||||
|
import com.bitchat.android.nostr.geohashesForSampling
|
||||||
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
import com.bitchat.android.ui.theme.BASE_FONT_SIZE
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
@ -61,6 +63,7 @@ fun LocationChannelsSheet(
|
|||||||
// Observe location manager state
|
// Observe location manager state
|
||||||
val permissionState by locationManager.permissionState.collectAsStateWithLifecycle()
|
val permissionState by locationManager.permissionState.collectAsStateWithLifecycle()
|
||||||
val availableChannels by locationManager.availableChannels.collectAsStateWithLifecycle()
|
val availableChannels by locationManager.availableChannels.collectAsStateWithLifecycle()
|
||||||
|
val notesRevealed by NearbyNotesController.shared.revealed.collectAsStateWithLifecycle()
|
||||||
val selectedChannel by locationManager.selectedChannel.collectAsStateWithLifecycle()
|
val selectedChannel by locationManager.selectedChannel.collectAsStateWithLifecycle()
|
||||||
val locationNames by locationManager.locationNames.collectAsStateWithLifecycle()
|
val locationNames by locationManager.locationNames.collectAsStateWithLifecycle()
|
||||||
val appLocationEnabled by locationManager.locationServicesEnabled.collectAsStateWithLifecycle()
|
val appLocationEnabled by locationManager.locationServicesEnabled.collectAsStateWithLifecycle()
|
||||||
@ -534,9 +537,13 @@ fun LocationChannelsSheet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sampling management: update sampling when channels/bookmarks change
|
// Sampling management: update sampling when channels/bookmarks change
|
||||||
LaunchedEffect(isPresented, availableChannels, bookmarks) {
|
LaunchedEffect(isPresented, availableChannels, bookmarks, notesRevealed) {
|
||||||
if (isPresented) {
|
if (isPresented) {
|
||||||
val geohashes = (availableChannels.map { it.geohash } + bookmarks).toSet().toList()
|
val geohashes = geohashesForSampling(
|
||||||
|
availableChannels = availableChannels,
|
||||||
|
bookmarks = bookmarks,
|
||||||
|
notesRevealed = notesRevealed,
|
||||||
|
)
|
||||||
viewModel.beginGeohashSampling(geohashes)
|
viewModel.beginGeohashSampling(geohashes)
|
||||||
} else {
|
} else {
|
||||||
viewModel.endGeohashSampling()
|
viewModel.endGeohashSampling()
|
||||||
|
|||||||
@ -32,6 +32,7 @@ import com.bitchat.android.core.ui.component.sheet.BitchatSheetTitle
|
|||||||
import com.bitchat.android.geohash.GeohashChannelLevel
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
import com.bitchat.android.geohash.LocationChannelManager
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
import com.bitchat.android.nostr.LocationNotesManager
|
import com.bitchat.android.nostr.LocationNotesManager
|
||||||
|
import com.bitchat.android.nostr.NearbyNotesController
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -58,12 +59,15 @@ fun LocationNotesSheet(
|
|||||||
// Managers
|
// Managers
|
||||||
val notesManager = remember { LocationNotesManager.getInstance() }
|
val notesManager = remember { LocationNotesManager.getInstance() }
|
||||||
val locationManager = remember { LocationChannelManager.getInstance(context) }
|
val locationManager = remember { LocationChannelManager.getInstance(context) }
|
||||||
|
val nearbyNotesController = remember { NearbyNotesController.shared }
|
||||||
|
|
||||||
// State
|
// State
|
||||||
val notes by notesManager.notes.collectAsStateWithLifecycle()
|
val notes by notesManager.notes.collectAsStateWithLifecycle()
|
||||||
val state by notesManager.state.collectAsStateWithLifecycle(LocationNotesManager.State.IDLE)
|
val state by notesManager.state.collectAsStateWithLifecycle(LocationNotesManager.State.IDLE)
|
||||||
val errorMessage by notesManager.errorMessage.collectAsStateWithLifecycle()
|
val errorMessage by notesManager.errorMessage.collectAsStateWithLifecycle()
|
||||||
val initialLoadComplete by notesManager.initialLoadComplete.collectAsStateWithLifecycle(false)
|
val initialLoadComplete by notesManager.initialLoadComplete.collectAsStateWithLifecycle(false)
|
||||||
|
val permissionState by locationManager.permissionState.collectAsStateWithLifecycle()
|
||||||
|
val locationEnabled by locationManager.effectiveLocationEnabled.collectAsStateWithLifecycle(false)
|
||||||
|
|
||||||
// SIMPLIFIED: Get count directly from notes list (no separate counter needed)
|
// SIMPLIFIED: Get count directly from notes list (no separate counter needed)
|
||||||
val count = notes.size
|
val count = notes.size
|
||||||
@ -94,15 +98,24 @@ fun LocationNotesSheet(
|
|||||||
locationManager.refreshChannels()
|
locationManager.refreshChannels()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Effect to set geohash when sheet opens
|
// Opening the notes sheet is an explicit reveal. The balanced hold lets
|
||||||
LaunchedEffect(geohash) {
|
// the mesh timeline keep the shared subscription alive after dismissal.
|
||||||
notesManager.setGeohash(geohash)
|
DisposableEffect(
|
||||||
}
|
geohash,
|
||||||
|
locationEnabled,
|
||||||
// Cleanup when sheet closes
|
permissionState,
|
||||||
DisposableEffect(Unit) {
|
nearbyNotesController,
|
||||||
|
) {
|
||||||
|
nearbyNotesController.updateAvailability(
|
||||||
|
locationEnabled = locationEnabled,
|
||||||
|
locationAuthorized =
|
||||||
|
permissionState == LocationChannelManager.PermissionState.AUTHORIZED,
|
||||||
|
buildingGeohash = geohash,
|
||||||
|
)
|
||||||
|
nearbyNotesController.activate()
|
||||||
|
nearbyNotesController.reveal()
|
||||||
onDispose {
|
onDispose {
|
||||||
notesManager.cancel()
|
nearbyNotesController.deactivate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">فتح قسم حول</string>
|
<string name="cd_open_about">فتح قسم حول</string>
|
||||||
|
<string name="nearby_notes_reveal">تحقّق من الملاحظات المتروكة هنا</string>
|
||||||
|
<string name="nearby_notes_one">تُركت ملاحظة واحدة هنا — انقر للقراءة</string>
|
||||||
|
<string name="nearby_notes_many">تُركت %d ملاحظات هنا — انقر للقراءة</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">পরিচিতি খুলুন</string>
|
<string name="cd_open_about">পরিচিতি খুলুন</string>
|
||||||
|
<string name="nearby_notes_reveal">এখানে রাখা নোট আছে কি না দেখুন</string>
|
||||||
|
<string name="nearby_notes_one">এখানে 1টি নোট রাখা আছে — পড়তে ট্যাপ করুন</string>
|
||||||
|
<string name="nearby_notes_many">এখানে %dটি নোট রাখা আছে — পড়তে ট্যাপ করুন</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -402,4 +402,7 @@
|
|||||||
<string name="verify_success_body">Du hast %1$s verifiziert</string>
|
<string name="verify_success_body">Du hast %1$s verifiziert</string>
|
||||||
<string name="verify_success_system_message">verifiziert %1$s</string>
|
<string name="verify_success_system_message">verifiziert %1$s</string>
|
||||||
<string name="cd_open_about">Info öffnen</string>
|
<string name="cd_open_about">Info öffnen</string>
|
||||||
|
<string name="nearby_notes_reveal">nachsehen, ob hier notizen hinterlassen wurden</string>
|
||||||
|
<string name="nearby_notes_one">1 notiz hier hinterlassen — tippen zum lesen</string>
|
||||||
|
<string name="nearby_notes_many">%d notizen hier hinterlassen — tippen zum lesen</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">Verificaste a %1$s</string>
|
<string name="verify_success_body">Verificaste a %1$s</string>
|
||||||
<string name="verify_success_system_message">verificado %1$s</string>
|
<string name="verify_success_system_message">verificado %1$s</string>
|
||||||
<string name="cd_open_about">Abrir Acerca de</string>
|
<string name="cd_open_about">Abrir Acerca de</string>
|
||||||
|
<string name="nearby_notes_reveal">buscar notas dejadas aquí</string>
|
||||||
|
<string name="nearby_notes_one">1 nota dejada aquí — toca para leer</string>
|
||||||
|
<string name="nearby_notes_many">%d notas dejadas aquí — toca para leer</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">باز کردن درباره</string>
|
<string name="cd_open_about">باز کردن درباره</string>
|
||||||
|
<string name="nearby_notes_reveal">یادداشتهای باقیمانده در اینجا را بررسی کنید</string>
|
||||||
|
<string name="nearby_notes_one">۱ یادداشت اینجا باقی مانده — برای خواندن ضربه بزنید</string>
|
||||||
|
<string name="nearby_notes_many">%d یادداشت اینجا باقی مانده — برای خواندن ضربه بزنید</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -400,4 +400,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Buksan ang Tungkol</string>
|
<string name="cd_open_about">Buksan ang Tungkol</string>
|
||||||
|
<string name="nearby_notes_reveal">tingnan kung may mga note na naiwan dito</string>
|
||||||
|
<string name="nearby_notes_one">1 note ang naiwan dito — i-tap para basahin</string>
|
||||||
|
<string name="nearby_notes_many">%d note ang naiwan dito — i-tap para basahin</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -414,4 +414,7 @@
|
|||||||
<string name="verify_success_body">Vous avez vérifié %1$s</string>
|
<string name="verify_success_body">Vous avez vérifié %1$s</string>
|
||||||
<string name="verify_success_system_message">vérifié %1$s</string>
|
<string name="verify_success_system_message">vérifié %1$s</string>
|
||||||
<string name="cd_open_about">Ouvrir À propos</string>
|
<string name="cd_open_about">Ouvrir À propos</string>
|
||||||
|
<string name="nearby_notes_reveal">vérifier s\'il y a des notes laissées ici</string>
|
||||||
|
<string name="nearby_notes_one">1 note laissée ici — appuyez pour lire</string>
|
||||||
|
<string name="nearby_notes_many">%d notes laissées ici — appuyez pour lire</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -54,4 +54,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">פתיחת אודות</string>
|
<string name="cd_open_about">פתיחת אודות</string>
|
||||||
|
<string name="nearby_notes_reveal">בדיקה אם הושארו כאן פתקים</string>
|
||||||
|
<string name="nearby_notes_one">פתק אחד הושאר כאן — הקש לקריאה</string>
|
||||||
|
<string name="nearby_notes_many">%d פתקים הושארו כאן — הקש לקריאה</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">परिचय खोलें</string>
|
<string name="cd_open_about">परिचय खोलें</string>
|
||||||
|
<string name="nearby_notes_reveal">देखें कि यहाँ नोट छोड़े गए हैं या नहीं</string>
|
||||||
|
<string name="nearby_notes_one">यहाँ 1 नोट छोड़ा गया है — पढ़ने के लिए टैप करें</string>
|
||||||
|
<string name="nearby_notes_many">यहाँ %d नोट छोड़े गए हैं — पढ़ने के लिए टैप करें</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Buka Tentang</string>
|
<string name="cd_open_about">Buka Tentang</string>
|
||||||
|
<string name="nearby_notes_reveal">periksa catatan yang ditinggalkan di sini</string>
|
||||||
|
<string name="nearby_notes_one">1 catatan ditinggalkan di sini — ketuk untuk membaca</string>
|
||||||
|
<string name="nearby_notes_many">%d catatan ditinggalkan di sini — ketuk untuk membaca</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -434,4 +434,7 @@
|
|||||||
<string name="verify_success_body">Hai verificato %1$s</string>
|
<string name="verify_success_body">Hai verificato %1$s</string>
|
||||||
<string name="verify_success_system_message">verificato %1$s</string>
|
<string name="verify_success_system_message">verificato %1$s</string>
|
||||||
<string name="cd_open_about">Apri Informazioni</string>
|
<string name="cd_open_about">Apri Informazioni</string>
|
||||||
|
<string name="nearby_notes_reveal">controlla se ci sono note lasciate qui</string>
|
||||||
|
<string name="nearby_notes_one">1 nota lasciata qui — tocca per leggere</string>
|
||||||
|
<string name="nearby_notes_many">%d note lasciate qui — tocca per leggere</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">%1$s を検証しました</string>
|
<string name="verify_success_body">%1$s を検証しました</string>
|
||||||
<string name="verify_success_system_message">%1$s を検証しました</string>
|
<string name="verify_success_system_message">%1$s を検証しました</string>
|
||||||
<string name="cd_open_about">このアプリについてを開く</string>
|
<string name="cd_open_about">このアプリについてを開く</string>
|
||||||
|
<string name="nearby_notes_reveal">ここに残されたメモを確認</string>
|
||||||
|
<string name="nearby_notes_one">ここに1件のメモがあります — タップして読む</string>
|
||||||
|
<string name="nearby_notes_many">ここに%d件のメモがあります — タップして読む</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">აპის შესახებ გახსნა</string>
|
<string name="cd_open_about">აპის შესახებ გახსნა</string>
|
||||||
|
<string name="nearby_notes_reveal">აქ დატოვებული ჩანაწერების შემოწმება</string>
|
||||||
|
<string name="nearby_notes_one">აქ 1 ჩანაწერია დატოვებული — წასაკითხად შეეხეთ</string>
|
||||||
|
<string name="nearby_notes_many">აქ %d ჩანაწერია დატოვებული — წასაკითხად შეეხეთ</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">정보 열기</string>
|
<string name="cd_open_about">정보 열기</string>
|
||||||
|
<string name="nearby_notes_reveal">여기 남겨진 쪽지 확인</string>
|
||||||
|
<string name="nearby_notes_one">여기 남겨진 쪽지 1개 — 탭하여 읽기</string>
|
||||||
|
<string name="nearby_notes_many">여기 남겨진 쪽지 %d개 — 탭하여 읽기</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -414,4 +414,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Sokafy ny momba</string>
|
<string name="cd_open_about">Sokafy ny momba</string>
|
||||||
|
<string name="nearby_notes_reveal">hizaha raha misy naoty navela teto</string>
|
||||||
|
<string name="nearby_notes_one">naoty 1 no navela teto — tsindrio raha hamaky</string>
|
||||||
|
<string name="nearby_notes_many">naoty %d no navela teto — tsindrio raha hamaky</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -41,4 +41,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Buka Perihal</string>
|
<string name="cd_open_about">Buka Perihal</string>
|
||||||
|
<string name="nearby_notes_reveal">semak nota yang ditinggalkan di sini</string>
|
||||||
|
<string name="nearby_notes_one">1 nota ditinggalkan di sini — ketik untuk baca</string>
|
||||||
|
<string name="nearby_notes_many">%d nota ditinggalkan di sini — ketik untuk baca</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -400,4 +400,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">परिचय खोल्नुहोस्</string>
|
<string name="cd_open_about">परिचय खोल्नुहोस्</string>
|
||||||
|
<string name="nearby_notes_reveal">यहाँ छोडिएका नोटहरू छन् कि हेर्नुहोस्</string>
|
||||||
|
<string name="nearby_notes_one">यहाँ 1 नोट छोडिएको छ — पढ्न ट्याप गर्नुहोस्</string>
|
||||||
|
<string name="nearby_notes_many">यहाँ %d नोटहरू छोडिएका छन् — पढ्न ट्याप गर्नुहोस्</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -432,4 +432,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Info openen</string>
|
<string name="cd_open_about">Info openen</string>
|
||||||
|
<string name="nearby_notes_reveal">kijk of hier notities zijn achtergelaten</string>
|
||||||
|
<string name="nearby_notes_one">1 notitie hier achtergelaten — tik om te lezen</string>
|
||||||
|
<string name="nearby_notes_many">%d notities hier achtergelaten — tik om te lezen</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">ایپ بارے کھولو</string>
|
<string name="cd_open_about">ایپ بارے کھولو</string>
|
||||||
|
<string name="nearby_notes_reveal">ایتھے چھڈے نوٹس ویکھو</string>
|
||||||
|
<string name="nearby_notes_one">ایتھے 1 نوٹ چھڈیا گیا — پڑھݨ لئی ٹیپ کرو</string>
|
||||||
|
<string name="nearby_notes_many">ایتھے %d نوٹس چھڈے گئے — پڑھݨ لئی ٹیپ کرو</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -54,4 +54,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Otwórz informacje</string>
|
<string name="cd_open_about">Otwórz informacje</string>
|
||||||
|
<string name="nearby_notes_reveal">sprawdź, czy zostawiono tutaj notatki</string>
|
||||||
|
<string name="nearby_notes_one">1 notatka zostawiona tutaj — stuknij, aby przeczytać</string>
|
||||||
|
<string name="nearby_notes_many">%d notatek zostawionych tutaj — stuknij, aby przeczytać</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -400,4 +400,7 @@
|
|||||||
<string name="verify_success_title">Verificado</string>
|
<string name="verify_success_title">Verificado</string>
|
||||||
<string name="verify_success_body">Você verificou %1$s</string>
|
<string name="verify_success_body">Você verificou %1$s</string>
|
||||||
<string name="verify_success_system_message">verificou %1$s</string>
|
<string name="verify_success_system_message">verificou %1$s</string>
|
||||||
|
<string name="nearby_notes_reveal">ver se há notas deixadas aqui</string>
|
||||||
|
<string name="nearby_notes_one">1 nota deixada aqui — toque para ler</string>
|
||||||
|
<string name="nearby_notes_many">%d notas deixadas aqui — toque para ler</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">Você verificou %1$s</string>
|
<string name="verify_success_body">Você verificou %1$s</string>
|
||||||
<string name="verify_success_system_message">verificou %1$s</string>
|
<string name="verify_success_system_message">verificou %1$s</string>
|
||||||
<string name="cd_open_about">Abrir Sobre</string>
|
<string name="cd_open_about">Abrir Sobre</string>
|
||||||
|
<string name="nearby_notes_reveal">ver se há notas deixadas aqui</string>
|
||||||
|
<string name="nearby_notes_one">1 nota deixada aqui — toque para ler</string>
|
||||||
|
<string name="nearby_notes_many">%d notas deixadas aqui — toque para ler</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -390,4 +390,7 @@
|
|||||||
<string name="verify_success_body">Вы проверили %1$s</string>
|
<string name="verify_success_body">Вы проверили %1$s</string>
|
||||||
<string name="verify_success_system_message">проверен %1$s</string>
|
<string name="verify_success_system_message">проверен %1$s</string>
|
||||||
<string name="cd_open_about">Открыть раздел «О приложении»</string>
|
<string name="cd_open_about">Открыть раздел «О приложении»</string>
|
||||||
|
<string name="nearby_notes_reveal">проверить, есть ли здесь заметки</string>
|
||||||
|
<string name="nearby_notes_one">здесь оставлена 1 заметка — нажмите, чтобы прочитать</string>
|
||||||
|
<string name="nearby_notes_many">здесь оставлено заметок: %d — нажмите, чтобы прочитать</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Öppna Om</string>
|
<string name="cd_open_about">Öppna Om</string>
|
||||||
|
<string name="nearby_notes_reveal">kolla om anteckningar lämnats här</string>
|
||||||
|
<string name="nearby_notes_one">1 anteckning lämnad här — tryck för att läsa</string>
|
||||||
|
<string name="nearby_notes_many">%d anteckningar lämnade här — tryck för att läsa</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -41,4 +41,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">அறிமுகத்தைத் திற</string>
|
<string name="cd_open_about">அறிமுகத்தைத் திற</string>
|
||||||
|
<string name="nearby_notes_reveal">இங்கே விடப்பட்ட குறிப்புகள் உள்ளதா எனப் பார்க்கவும்</string>
|
||||||
|
<string name="nearby_notes_one">இங்கே 1 குறிப்பு விடப்பட்டுள்ளது — படிக்க தட்டவும்</string>
|
||||||
|
<string name="nearby_notes_many">இங்கே %d குறிப்புகள் விடப்பட்டுள்ளன — படிக்க தட்டவும்</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">เปิดเกี่ยวกับ</string>
|
<string name="cd_open_about">เปิดเกี่ยวกับ</string>
|
||||||
|
<string name="nearby_notes_reveal">ดูว่ามีโน้ตทิ้งไว้ที่นี่หรือไม่</string>
|
||||||
|
<string name="nearby_notes_one">มี 1 โน้ตทิ้งไว้ที่นี่ — แตะเพื่ออ่าน</string>
|
||||||
|
<string name="nearby_notes_many">มี %d โน้ตทิ้งไว้ที่นี่ — แตะเพื่ออ่าน</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Hakkında’yı aç</string>
|
<string name="cd_open_about">Hakkında’yı aç</string>
|
||||||
|
<string name="nearby_notes_reveal">buraya bırakılan notlara bak</string>
|
||||||
|
<string name="nearby_notes_one">buraya 1 not bırakıldı — okumak için dokun</string>
|
||||||
|
<string name="nearby_notes_many">buraya %d not bırakıldı — okumak için dokun</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -41,4 +41,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Відкрити розділ «Про застосунок»</string>
|
<string name="cd_open_about">Відкрити розділ «Про застосунок»</string>
|
||||||
|
<string name="nearby_notes_reveal">перевірити, чи залишено тут нотатки</string>
|
||||||
|
<string name="nearby_notes_one">тут залишено 1 нотатку — торкніться, щоб прочитати</string>
|
||||||
|
<string name="nearby_notes_many">тут залишено %d нотаток — торкніться, щоб прочитати</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -401,4 +401,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">تعارف کھولیں</string>
|
<string name="cd_open_about">تعارف کھولیں</string>
|
||||||
|
<string name="nearby_notes_reveal">دیکھیں کہ یہاں نوٹ چھوڑے گئے ہیں یا نہیں</string>
|
||||||
|
<string name="nearby_notes_one">یہاں 1 نوٹ چھوڑا گیا ہے — پڑھنے کے لیے تھپتھپائیں</string>
|
||||||
|
<string name="nearby_notes_many">یہاں %d نوٹ چھوڑے گئے ہیں — پڑھنے کے لیے تھپتھپائیں</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -388,4 +388,7 @@
|
|||||||
<string name="verify_success_body">You verified %1$s</string>
|
<string name="verify_success_body">You verified %1$s</string>
|
||||||
<string name="verify_success_system_message">verified %1$s</string>
|
<string name="verify_success_system_message">verified %1$s</string>
|
||||||
<string name="cd_open_about">Mở phần Giới thiệu</string>
|
<string name="cd_open_about">Mở phần Giới thiệu</string>
|
||||||
|
<string name="nearby_notes_reveal">kiểm tra ghi chú để lại ở đây</string>
|
||||||
|
<string name="nearby_notes_one">có 1 ghi chú để lại ở đây — chạm để đọc</string>
|
||||||
|
<string name="nearby_notes_many">có %d ghi chú để lại ở đây — chạm để đọc</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -53,5 +53,7 @@
|
|||||||
<string name="verify_success_title">已验证</string>
|
<string name="verify_success_title">已验证</string>
|
||||||
<string name="verify_success_body">你已验证 %1$s</string>
|
<string name="verify_success_body">你已验证 %1$s</string>
|
||||||
<string name="verify_success_system_message">已验证 %1$s</string>
|
<string name="verify_success_system_message">已验证 %1$s</string>
|
||||||
|
<string name="nearby_notes_reveal">查看这里留下的留言</string>
|
||||||
|
<string name="nearby_notes_one">这里留有 1 条留言 — 点按阅读</string>
|
||||||
|
<string name="nearby_notes_many">这里留有 %d 条留言 — 点按阅读</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@ -53,5 +53,7 @@
|
|||||||
<string name="verify_success_title">已验证</string>
|
<string name="verify_success_title">已验证</string>
|
||||||
<string name="verify_success_body">你已验证 %1$s</string>
|
<string name="verify_success_body">你已验证 %1$s</string>
|
||||||
<string name="verify_success_system_message">已验证 %1$s</string>
|
<string name="verify_success_system_message">已验证 %1$s</string>
|
||||||
|
<string name="nearby_notes_reveal">查看這裡留下的留言</string>
|
||||||
|
<string name="nearby_notes_one">這裡留有 1 則留言 — 點按閱讀</string>
|
||||||
|
<string name="nearby_notes_many">這裡留有 %d 則留言 — 點按閱讀</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@ -413,4 +413,7 @@
|
|||||||
<string name="verify_success_body">你已验证 %1$s</string>
|
<string name="verify_success_body">你已验证 %1$s</string>
|
||||||
<string name="verify_success_system_message">已验证 %1$s</string>
|
<string name="verify_success_system_message">已验证 %1$s</string>
|
||||||
<string name="cd_open_about">打开“关于”</string>
|
<string name="cd_open_about">打开“关于”</string>
|
||||||
|
<string name="nearby_notes_reveal">查看这里留下的留言</string>
|
||||||
|
<string name="nearby_notes_one">这里留有 1 条留言 — 点按阅读</string>
|
||||||
|
<string name="nearby_notes_many">这里留有 %d 条留言 — 点按阅读</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@ -234,6 +234,10 @@
|
|||||||
<string name="location_level_region">region</string>
|
<string name="location_level_region">region</string>
|
||||||
|
|
||||||
<!-- Location notes sheet -->
|
<!-- Location notes sheet -->
|
||||||
|
<string name="nearby_notes_reveal">check for notes left here</string>
|
||||||
|
<string name="nearby_notes_one">1 note left here — tap to read</string>
|
||||||
|
<!-- Explicit one/many copy mirrors iOS across locales without CLDR quantity gaps. -->
|
||||||
|
<string name="nearby_notes_many" tools:ignore="PluralsCandidate">%d notes left here — tap to read</string>
|
||||||
<plurals name="location_notes_title">
|
<plurals name="location_notes_title">
|
||||||
<item quantity="one">#%1$s ± 1 • %2$d note</item>
|
<item quantity="one">#%1$s ± 1 • %2$d note</item>
|
||||||
<item quantity="other">#%1$s ± 1 • %2$d notes</item>
|
<item quantity="other">#%1$s ± 1 • %2$d notes</item>
|
||||||
|
|||||||
@ -0,0 +1,159 @@
|
|||||||
|
package com.bitchat.android.nostr
|
||||||
|
|
||||||
|
import com.bitchat.android.geohash.GeohashChannel
|
||||||
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class NearbyNotesControllerTest {
|
||||||
|
private val subscriptions = mutableListOf<String>()
|
||||||
|
private var unsubscribeCount = 0
|
||||||
|
|
||||||
|
private fun controller() = NearbyNotesController(
|
||||||
|
subscribe = subscriptions::add,
|
||||||
|
unsubscribe = { unsubscribeCount += 1 },
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun foregroundController() = controller().also {
|
||||||
|
it.updateAppForeground(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `active mesh timeline does not subscribe before explicit reveal`() {
|
||||||
|
val controller = foregroundController()
|
||||||
|
|
||||||
|
controller.updateAvailability(
|
||||||
|
locationEnabled = true,
|
||||||
|
locationAuthorized = true,
|
||||||
|
buildingGeohash = "u4pruydq",
|
||||||
|
)
|
||||||
|
controller.activate()
|
||||||
|
|
||||||
|
assertTrue(controller.offersRevealHint())
|
||||||
|
assertTrue(subscriptions.isEmpty())
|
||||||
|
|
||||||
|
controller.reveal()
|
||||||
|
|
||||||
|
assertFalse(controller.offersRevealHint())
|
||||||
|
assertEquals(listOf("u4pruydq"), subscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `reveal remains dormant until a nearby notes surface is active`() {
|
||||||
|
val controller = foregroundController()
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
|
||||||
|
controller.reveal()
|
||||||
|
|
||||||
|
assertTrue(subscriptions.isEmpty())
|
||||||
|
|
||||||
|
controller.activate()
|
||||||
|
|
||||||
|
assertEquals(listOf("u4pruydq"), subscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `last deactivate unsubscribes exactly once`() {
|
||||||
|
val controller = foregroundController()
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
controller.reveal()
|
||||||
|
controller.activate()
|
||||||
|
controller.activate()
|
||||||
|
|
||||||
|
controller.deactivate()
|
||||||
|
assertEquals(0, unsubscribeCount)
|
||||||
|
|
||||||
|
controller.deactivate()
|
||||||
|
controller.deactivate()
|
||||||
|
|
||||||
|
assertEquals(1, unsubscribeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `backgrounding closes the subscription and foregrounding restores it`() {
|
||||||
|
val controller = foregroundController()
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
controller.activate()
|
||||||
|
controller.reveal()
|
||||||
|
|
||||||
|
controller.updateAppForeground(false)
|
||||||
|
|
||||||
|
assertEquals(1, unsubscribeCount)
|
||||||
|
assertTrue(controller.revealed.value)
|
||||||
|
|
||||||
|
controller.updateAppForeground(false)
|
||||||
|
assertEquals(1, unsubscribeCount)
|
||||||
|
|
||||||
|
controller.updateAppForeground(true)
|
||||||
|
assertEquals(listOf("u4pruydq", "u4pruydq"), subscriptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `disable and permission revocation close the live subscription`() {
|
||||||
|
val controller = foregroundController()
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
controller.activate()
|
||||||
|
controller.reveal()
|
||||||
|
|
||||||
|
controller.updateAvailability(false, true, "u4pruydq")
|
||||||
|
assertEquals(1, unsubscribeCount)
|
||||||
|
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
assertEquals(listOf("u4pruydq", "u4pruydq"), subscriptions)
|
||||||
|
|
||||||
|
controller.updateAvailability(true, false, "u4pruydq")
|
||||||
|
assertEquals(2, unsubscribeCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `moving building cells releases old subscription before retargeting`() {
|
||||||
|
val events = mutableListOf<String>()
|
||||||
|
val controller = NearbyNotesController(
|
||||||
|
subscribe = { events += "subscribe:$it" },
|
||||||
|
unsubscribe = { events += "unsubscribe" },
|
||||||
|
)
|
||||||
|
controller.updateAppForeground(true)
|
||||||
|
controller.updateAvailability(true, true, "u4pruydq")
|
||||||
|
controller.activate()
|
||||||
|
controller.reveal()
|
||||||
|
|
||||||
|
controller.updateAvailability(true, true, "u4pruydr")
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf(
|
||||||
|
"subscribe:u4pruydq",
|
||||||
|
"unsubscribe",
|
||||||
|
"subscribe:u4pruydr",
|
||||||
|
),
|
||||||
|
events,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `building sampling is excluded until reveal while bookmarks remain eligible`() {
|
||||||
|
val channels = listOf(
|
||||||
|
GeohashChannel(GeohashChannelLevel.BUILDING, "u4pruydq"),
|
||||||
|
GeohashChannel(GeohashChannelLevel.BLOCK, "u4pruyd"),
|
||||||
|
GeohashChannel(GeohashChannelLevel.CITY, "u4pru"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
listOf("u4pruyd", "u4pru", "saved123"),
|
||||||
|
geohashesForSampling(
|
||||||
|
availableChannels = channels,
|
||||||
|
bookmarks = listOf("saved123"),
|
||||||
|
notesRevealed = false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
assertEquals(
|
||||||
|
listOf("u4pruydq", "u4pruyd", "u4pru", "saved123"),
|
||||||
|
geohashesForSampling(
|
||||||
|
availableChannels = channels,
|
||||||
|
bookmarks = listOf("saved123"),
|
||||||
|
notesRevealed = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user