mirror of
https://github.com/permissionlesstech/bitchat-android.git
synced 2026-07-25 21:05:24 +00:00
refactor
This commit is contained in:
@@ -598,42 +598,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
PoWPreferenceManager.init(this@MainActivity)
|
PoWPreferenceManager.init(this@MainActivity)
|
||||||
Log.d("MainActivity", "PoW preferences initialized")
|
Log.d("MainActivity", "PoW preferences initialized")
|
||||||
|
|
||||||
// SIMPLIFIED: Initialize Location Notes Manager only (no separate counter)
|
// Initialize Location Notes Manager (extracted to separate file)
|
||||||
com.bitchat.android.nostr.LocationNotesManager.getInstance().initialize(
|
com.bitchat.android.nostr.LocationNotesInitializer.initialize(this@MainActivity)
|
||||||
relayManager = { com.bitchat.android.nostr.NostrRelayManager.getInstance(this@MainActivity) },
|
|
||||||
subscribe = { filter, id, handler ->
|
|
||||||
// CRITICAL FIX: Extract geohash properly from filter using getGeohash() method
|
|
||||||
val geohashFromFilter = filter.getGeohash() ?: run {
|
|
||||||
Log.e("MainActivity", "❌ Cannot extract geohash from filter for location notes")
|
|
||||||
return@initialize id // Return subscription ID even on error
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("MainActivity", "📍 Location Notes subscribing to geohash: $geohashFromFilter")
|
|
||||||
|
|
||||||
com.bitchat.android.nostr.NostrRelayManager.getInstance(this@MainActivity).subscribeForGeohash(
|
|
||||||
geohash = geohashFromFilter,
|
|
||||||
filter = filter,
|
|
||||||
id = id,
|
|
||||||
handler = handler,
|
|
||||||
includeDefaults = true,
|
|
||||||
nRelays = 5
|
|
||||||
)
|
|
||||||
},
|
|
||||||
unsubscribe = { id ->
|
|
||||||
com.bitchat.android.nostr.NostrRelayManager.getInstance(this@MainActivity).unsubscribe(id)
|
|
||||||
},
|
|
||||||
sendEvent = { event, relayUrls ->
|
|
||||||
if (relayUrls != null) {
|
|
||||||
com.bitchat.android.nostr.NostrRelayManager.getInstance(this@MainActivity).sendEvent(event, relayUrls)
|
|
||||||
} else {
|
|
||||||
com.bitchat.android.nostr.NostrRelayManager.getInstance(this@MainActivity).sendEvent(event)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
deriveIdentity = { geohash ->
|
|
||||||
com.bitchat.android.nostr.NostrIdentityBridge.deriveIdentity(geohash, this@MainActivity)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Log.d("MainActivity", "✅ Location Notes Manager initialized")
|
|
||||||
|
|
||||||
// Ensure all permissions are still granted (user might have revoked in settings)
|
// Ensure all permissions are still granted (user might have revoked in settings)
|
||||||
if (!permissionManager.areAllPermissionsGranted()) {
|
if (!permissionManager.areAllPermissionsGranted()) {
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.bitchat.android.nostr
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializer for LocationNotesManager with all dependencies
|
||||||
|
* Extracts initialization logic from MainActivity for better separation of concerns
|
||||||
|
*/
|
||||||
|
object LocationNotesInitializer {
|
||||||
|
|
||||||
|
private const val TAG = "LocationNotesInitializer"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize LocationNotesManager with all required dependencies
|
||||||
|
*
|
||||||
|
* @param context Application context
|
||||||
|
* @return true if initialization succeeded, false otherwise
|
||||||
|
*/
|
||||||
|
fun initialize(context: Context): Boolean {
|
||||||
|
return try {
|
||||||
|
LocationNotesManager.getInstance().initialize(
|
||||||
|
relayManager = { NostrRelayManager.getInstance(context) },
|
||||||
|
subscribe = { filter, id, handler ->
|
||||||
|
// CRITICAL FIX: Extract geohash properly from filter using getGeohash() method
|
||||||
|
val geohashFromFilter = filter.getGeohash() ?: run {
|
||||||
|
Log.e(TAG, "❌ Cannot extract geohash from filter for location notes")
|
||||||
|
return@initialize id // Return subscription ID even on error
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "📍 Location Notes subscribing to geohash: $geohashFromFilter")
|
||||||
|
|
||||||
|
NostrRelayManager.getInstance(context).subscribeForGeohash(
|
||||||
|
geohash = geohashFromFilter,
|
||||||
|
filter = filter,
|
||||||
|
id = id,
|
||||||
|
handler = handler,
|
||||||
|
includeDefaults = true,
|
||||||
|
nRelays = 5
|
||||||
|
)
|
||||||
|
},
|
||||||
|
unsubscribe = { id ->
|
||||||
|
NostrRelayManager.getInstance(context).unsubscribe(id)
|
||||||
|
},
|
||||||
|
sendEvent = { event, relayUrls ->
|
||||||
|
if (relayUrls != null) {
|
||||||
|
NostrRelayManager.getInstance(context).sendEvent(event, relayUrls)
|
||||||
|
} else {
|
||||||
|
NostrRelayManager.getInstance(context).sendEvent(event)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deriveIdentity = { geohash ->
|
||||||
|
NostrIdentityBridge.deriveIdentity(geohash, context)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Log.d(TAG, "✅ Location Notes Manager initialized")
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "❌ Failed to initialize Location Notes Manager: ${e.message}", e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -536,16 +536,6 @@ private fun MainHeader(
|
|||||||
val bookmarksStore = remember { com.bitchat.android.geohash.GeohashBookmarksStore.getInstance(context) }
|
val bookmarksStore = remember { com.bitchat.android.geohash.GeohashBookmarksStore.getInstance(context) }
|
||||||
val bookmarks by bookmarksStore.bookmarks.observeAsState(emptyList())
|
val bookmarks by bookmarksStore.bookmarks.observeAsState(emptyList())
|
||||||
|
|
||||||
// SIMPLIFIED: Get notes count directly from LocationNotesManager (no separate counter)
|
|
||||||
val notesManager = remember { com.bitchat.android.nostr.LocationNotesManager.getInstance() }
|
|
||||||
val notes by notesManager.notes.observeAsState(emptyList())
|
|
||||||
val notesCount = notes.size
|
|
||||||
|
|
||||||
// Location channel manager for permission state
|
|
||||||
val locationManager = remember { com.bitchat.android.geohash.LocationChannelManager.getInstance(context) }
|
|
||||||
val permissionState by locationManager.permissionState.observeAsState()
|
|
||||||
val locationPermissionGranted = permissionState == PermissionState.AUTHORIZED
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
@@ -623,22 +613,11 @@ private fun MainHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location Notes button (mesh only, when location is authorized)
|
// Location Notes button (extracted to separate component)
|
||||||
// iOS: Shows to the left of #mesh badge when in mesh mode and location permitted
|
LocationNotesButton(
|
||||||
if (selectedLocationChannel is com.bitchat.android.geohash.ChannelID.Mesh && locationPermissionGranted) {
|
viewModel = viewModel,
|
||||||
val hasNotes = (notesCount ?: 0) > 0
|
onClick = onLocationNotesClick
|
||||||
IconButton(
|
)
|
||||||
onClick = onLocationNotesClick,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Outlined.Description, // "long.text.page.and.pencil" equivalent
|
|
||||||
contentDescription = stringResource(R.string.cd_location_notes),
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = if (hasNotes) colorScheme.primary else Color.Gray
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tor status dot when Tor is enabled
|
// Tor status dot when Tor is enabled
|
||||||
TorStatusDot(
|
TorStatusDot(
|
||||||
|
|||||||
@@ -539,61 +539,12 @@ private fun ChatDialogs(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Location notes sheet - matches iOS pattern exactly
|
// Location notes sheet (extracted to separate presenter)
|
||||||
if (showLocationNotesSheet) {
|
if (showLocationNotesSheet) {
|
||||||
val context = androidx.compose.ui.platform.LocalContext.current
|
LocationNotesSheetPresenter(
|
||||||
val locationManager = remember { com.bitchat.android.geohash.LocationChannelManager.getInstance(context) }
|
viewModel = viewModel,
|
||||||
val availableChannels by locationManager.availableChannels.observeAsState(emptyList())
|
onDismiss = onLocationNotesSheetDismiss
|
||||||
val nickname by viewModel.nickname.observeAsState("")
|
)
|
||||||
|
|
||||||
// iOS pattern: notesGeohash ?? LocationChannelManager.shared.availableChannels.first(where: { $0.level == .building })?.geohash
|
|
||||||
val buildingGeohash = availableChannels.firstOrNull { it.level == com.bitchat.android.geohash.GeohashChannelLevel.BUILDING }?.geohash
|
|
||||||
|
|
||||||
if (buildingGeohash != null) {
|
|
||||||
// Get location name from locationManager
|
|
||||||
val locationNames by locationManager.locationNames.observeAsState(emptyMap())
|
|
||||||
val locationName = locationNames[com.bitchat.android.geohash.GeohashChannelLevel.BUILDING]
|
|
||||||
?: locationNames[com.bitchat.android.geohash.GeohashChannelLevel.BLOCK]
|
|
||||||
|
|
||||||
LocationNotesSheet(
|
|
||||||
geohash = buildingGeohash,
|
|
||||||
locationName = locationName,
|
|
||||||
nickname = nickname,
|
|
||||||
onDismiss = onLocationNotesSheetDismiss
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// No building geohash available - show error state (matches iOS)
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = onLocationNotesSheetDismiss,
|
|
||||||
containerColor = MaterialTheme.colorScheme.surface
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(24.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Location Unavailable",
|
|
||||||
style = MaterialTheme.typography.titleMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
|
||||||
text = "Location permission is required for notes",
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Button(onClick = {
|
|
||||||
locationManager.enableLocationChannels()
|
|
||||||
locationManager.refreshChannels()
|
|
||||||
}) {
|
|
||||||
Text("Enable Location")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// User action sheet
|
// User action sheet
|
||||||
|
|||||||
@@ -594,69 +594,7 @@ class ChatViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Location notes subscription management moved to LocationNotesViewModelExtensions.kt
|
||||||
* SIMPLIFIED: Initialize location notes manager subscription (no separate counter)
|
|
||||||
* Subscribes/unsubscribes based on channel selection and location permission
|
|
||||||
*/
|
|
||||||
private fun initializeLocationNotesManagerSubscription() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
// Observe channel changes
|
|
||||||
selectedLocationChannel.observeForever { channel ->
|
|
||||||
updateLocationNotesSubscription(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* SIMPLIFIED: Update location notes subscription based on current channel and location permission
|
|
||||||
* iOS pattern: Subscribe when in mesh mode and location is authorized
|
|
||||||
*/
|
|
||||||
private fun updateLocationNotesSubscription(channel: com.bitchat.android.geohash.ChannelID?) {
|
|
||||||
try {
|
|
||||||
val locationManager = com.bitchat.android.geohash.LocationChannelManager.getInstance(getApplication())
|
|
||||||
val permissionState = locationManager.permissionState.value
|
|
||||||
val locationPermissionGranted = permissionState == com.bitchat.android.geohash.LocationChannelManager.PermissionState.AUTHORIZED
|
|
||||||
|
|
||||||
when (channel) {
|
|
||||||
is com.bitchat.android.geohash.ChannelID.Mesh -> {
|
|
||||||
// Mesh mode: subscribe to building-level geohash notes if location authorized
|
|
||||||
if (locationPermissionGranted) {
|
|
||||||
// Refresh location to get current building geohash
|
|
||||||
locationManager.refreshChannels()
|
|
||||||
|
|
||||||
// Get building-level geohash (precision 8, same as iOS)
|
|
||||||
val buildingGeohash = locationManager.availableChannels.value
|
|
||||||
?.firstOrNull { it.level == com.bitchat.android.geohash.GeohashChannelLevel.BUILDING }
|
|
||||||
?.geohash
|
|
||||||
|
|
||||||
if (buildingGeohash != null) {
|
|
||||||
Log.d(TAG, "📍 Subscribing to location notes for building geohash: $buildingGeohash")
|
|
||||||
com.bitchat.android.nostr.LocationNotesManager.getInstance().setGeohash(buildingGeohash)
|
|
||||||
} else {
|
|
||||||
// Cancel if no building geohash available
|
|
||||||
if (com.bitchat.android.nostr.LocationNotesManager.getInstance().geohash.value == null) {
|
|
||||||
com.bitchat.android.nostr.LocationNotesManager.getInstance().cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
com.bitchat.android.nostr.LocationNotesManager.getInstance().cancel()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is com.bitchat.android.geohash.ChannelID.Location -> {
|
|
||||||
// Location channel mode: cancel (only show in mesh mode)
|
|
||||||
com.bitchat.android.nostr.LocationNotesManager.getInstance().cancel()
|
|
||||||
}
|
|
||||||
null -> {
|
|
||||||
// Default to mesh behavior
|
|
||||||
if (locationPermissionGranted) {
|
|
||||||
locationManager.refreshChannels()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.w(TAG, "updateLocationNotesSubscription failed: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update reactive states for all connected peers (session states, fingerprints, nicknames, RSSI)
|
* Update reactive states for all connected peers (session states, fingerprints, nicknames, RSSI)
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package com.bitchat.android.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Description
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.bitchat.android.R
|
||||||
|
import com.bitchat.android.geohash.ChannelID
|
||||||
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
|
import com.bitchat.android.nostr.LocationNotesManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Location Notes button component for MainHeader
|
||||||
|
* Displays when in mesh mode and location permission is granted
|
||||||
|
* Icon turns primary color when notes exist, gray otherwise
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LocationNotesButton(
|
||||||
|
viewModel: ChatViewModel,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// Get channel and permission state
|
||||||
|
val selectedLocationChannel by viewModel.selectedLocationChannel.observeAsState()
|
||||||
|
val locationManager = remember { LocationChannelManager.getInstance(context) }
|
||||||
|
val permissionState by locationManager.permissionState.observeAsState()
|
||||||
|
val locationPermissionGranted = permissionState == LocationChannelManager.PermissionState.AUTHORIZED
|
||||||
|
|
||||||
|
// Get notes count from LocationNotesManager
|
||||||
|
val notesManager = remember { LocationNotesManager.getInstance() }
|
||||||
|
val notes by notesManager.notes.observeAsState(emptyList())
|
||||||
|
val notesCount = notes.size
|
||||||
|
|
||||||
|
// Only show in mesh mode when location is authorized (iOS pattern)
|
||||||
|
if (selectedLocationChannel is ChannelID.Mesh && locationPermissionGranted) {
|
||||||
|
val hasNotes = (notesCount ?: 0) > 0
|
||||||
|
IconButton(
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = modifier.size(24.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Description, // "long.text.page.and.pencil" equivalent
|
||||||
|
contentDescription = stringResource(R.string.cd_location_notes),
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = if (hasNotes) colorScheme.primary else Color.Gray
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.bitchat.android.ui
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter component for LocationNotesSheet
|
||||||
|
* Handles sheet presentation logic with proper error states
|
||||||
|
* Extracts this logic from ChatScreen for better separation of concerns
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun LocationNotesSheetPresenter(
|
||||||
|
viewModel: ChatViewModel,
|
||||||
|
onDismiss: () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val locationManager = remember { LocationChannelManager.getInstance(context) }
|
||||||
|
val availableChannels by locationManager.availableChannels.observeAsState(emptyList())
|
||||||
|
val nickname by viewModel.nickname.observeAsState("")
|
||||||
|
|
||||||
|
// iOS pattern: notesGeohash ?? LocationChannelManager.shared.availableChannels.first(where: { $0.level == .building })?.geohash
|
||||||
|
val buildingGeohash = availableChannels.firstOrNull { it.level == GeohashChannelLevel.BUILDING }?.geohash
|
||||||
|
|
||||||
|
if (buildingGeohash != null) {
|
||||||
|
// Get location name from locationManager
|
||||||
|
val locationNames by locationManager.locationNames.observeAsState(emptyMap())
|
||||||
|
val locationName = locationNames[GeohashChannelLevel.BUILDING]
|
||||||
|
?: locationNames[GeohashChannelLevel.BLOCK]
|
||||||
|
|
||||||
|
LocationNotesSheet(
|
||||||
|
geohash = buildingGeohash,
|
||||||
|
locationName = locationName,
|
||||||
|
nickname = nickname,
|
||||||
|
onDismiss = onDismiss
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// No building geohash available - show error state (matches iOS)
|
||||||
|
LocationNotesErrorSheet(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
locationManager = locationManager
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error sheet when location is unavailable
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun LocationNotesErrorSheet(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
locationManager: LocationChannelManager
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(24.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Location Unavailable",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(
|
||||||
|
text = "Location permission is required for notes",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Button(onClick = {
|
||||||
|
locationManager.enableLocationChannels()
|
||||||
|
locationManager.refreshChannels()
|
||||||
|
}) {
|
||||||
|
Text("Enable Location")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.bitchat.android.ui
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.bitchat.android.geohash.ChannelID
|
||||||
|
import com.bitchat.android.geohash.GeohashChannelLevel
|
||||||
|
import com.bitchat.android.geohash.LocationChannelManager
|
||||||
|
import com.bitchat.android.nostr.LocationNotesManager
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension functions for ChatViewModel to manage LocationNotesManager subscription
|
||||||
|
* Extracts location notes subscription logic for better separation of concerns
|
||||||
|
*/
|
||||||
|
|
||||||
|
private const val TAG = "LocationNotesVM"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize location notes manager subscription (no separate counter)
|
||||||
|
* Subscribes/unsubscribes based on channel selection and location permission
|
||||||
|
*/
|
||||||
|
fun ChatViewModel.initializeLocationNotesManagerSubscription() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Observe channel changes
|
||||||
|
selectedLocationChannel.observeForever { channel ->
|
||||||
|
updateLocationNotesSubscription(channel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update location notes subscription based on current channel and location permission
|
||||||
|
* iOS pattern: Subscribe when in mesh mode and location is authorized
|
||||||
|
*/
|
||||||
|
private fun ChatViewModel.updateLocationNotesSubscription(channel: ChannelID?) {
|
||||||
|
try {
|
||||||
|
val locationManager = LocationChannelManager.getInstance(getApplication<Application>())
|
||||||
|
val permissionState = locationManager.permissionState.value
|
||||||
|
val locationPermissionGranted = permissionState == LocationChannelManager.PermissionState.AUTHORIZED
|
||||||
|
|
||||||
|
when (channel) {
|
||||||
|
is ChannelID.Mesh -> {
|
||||||
|
// Mesh mode: subscribe to building-level geohash notes if location authorized
|
||||||
|
if (locationPermissionGranted) {
|
||||||
|
// Refresh location to get current building geohash
|
||||||
|
locationManager.refreshChannels()
|
||||||
|
|
||||||
|
// Get building-level geohash (precision 8, same as iOS)
|
||||||
|
val buildingGeohash = locationManager.availableChannels.value
|
||||||
|
?.firstOrNull { it.level == GeohashChannelLevel.BUILDING }
|
||||||
|
?.geohash
|
||||||
|
|
||||||
|
if (buildingGeohash != null) {
|
||||||
|
Log.d(TAG, "📍 Subscribing to location notes for building geohash: $buildingGeohash")
|
||||||
|
LocationNotesManager.getInstance().setGeohash(buildingGeohash)
|
||||||
|
} else {
|
||||||
|
// Cancel if no building geohash available
|
||||||
|
if (LocationNotesManager.getInstance().geohash.value == null) {
|
||||||
|
LocationNotesManager.getInstance().cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LocationNotesManager.getInstance().cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is ChannelID.Location -> {
|
||||||
|
// Location channel mode: cancel (only show in mesh mode)
|
||||||
|
LocationNotesManager.getInstance().cancel()
|
||||||
|
}
|
||||||
|
null -> {
|
||||||
|
// Default to mesh behavior
|
||||||
|
if (locationPermissionGranted) {
|
||||||
|
locationManager.refreshChannels()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "updateLocationNotesSubscription failed: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user