refactor(ble): Centralize BLE logic into a core module (#4550)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-20 06:41:52 -06:00 committed by GitHub
parent 7a68802bc2
commit 6bfa5b5f70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
214 changed files with 3471 additions and 2405 deletions

View file

@ -26,17 +26,17 @@ import android.nfc.NdefMessage
import android.nfc.NfcAdapter
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.ReportDrawnWhen
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.ui.platform.LocalView
import androidx.core.content.IntentCompat
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -46,7 +46,8 @@ import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.MainScreen
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.meshtastic.core.datastore.UiPreferencesDataSource
import no.nordicsemi.kotlin.ble.core.android.AndroidEnvironment
import no.nordicsemi.kotlin.ble.environment.android.compose.LocalEnvironmentOwner
import org.meshtastic.core.model.util.dispatchMeshtasticUri
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.strings.Res
@ -58,59 +59,64 @@ import org.meshtastic.feature.intro.AppIntroductionScreen
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
class MainActivity : ComponentActivity() {
private val model: UIViewModel by viewModels()
// This is aware of the Activity lifecycle and handles binding to the mesh service.
/**
* Activity-lifecycle-aware client that binds to the mesh service. Note: This is used implicitly as it registers
* itself as a LifecycleObserver in its init block.
*/
@Inject internal lateinit var meshServiceClient: MeshServiceClient
@Inject internal lateinit var uiPreferencesDataSource: UiPreferencesDataSource
@Inject internal lateinit var androidEnvironment: AndroidEnvironment
override fun onCreate(savedInstanceState: Bundle?) {
installSplashScreen()
enableEdgeToEdge(
// Disable three-button navbar scrim on pre-Q devices
navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT),
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Disable three-button navbar scrim
window.setNavigationBarContrastEnforced(false)
}
super.onCreate(savedInstanceState)
setContent {
val theme by model.theme.collectAsState()
val theme by model.theme.collectAsStateWithLifecycle()
val dynamic = theme == MODE_DYNAMIC
val dark =
when (theme) {
AppCompatDelegate.MODE_NIGHT_YES -> true
AppCompatDelegate.MODE_NIGHT_NO -> false
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> isSystemInDarkTheme()
else -> isSystemInDarkTheme()
}
AppTheme(dynamicColor = dynamic, darkTheme = dark) {
val view = LocalView.current
if (!view.isInEditMode) {
SideEffect { AppCompatDelegate.setDefaultNightMode(theme) }
}
// Apply modern edge-to-edge drawing with theme-aware system bars
enableEdgeToEdge(
statusBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { dark },
navigationBarStyle = SystemBarStyle.auto(Color.TRANSPARENT, Color.TRANSPARENT) { dark },
)
val appIntroCompleted by model.appIntroCompleted.collectAsStateWithLifecycle()
if (appIntroCompleted) {
MainScreen(uIViewModel = model)
} else {
AppIntroductionScreen(onDone = { model.onAppIntroCompleted() })
// Ensure the navigation bar remains seamless on modern Android versions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
window.isNavigationBarContrastEnforced = false
}
@Suppress("SpreadOperator")
CompositionLocalProvider(*(LocalEnvironmentOwner provides androidEnvironment)) {
AppTheme(dynamicColor = dynamic, darkTheme = dark) {
val appIntroCompleted by model.appIntroCompleted.collectAsStateWithLifecycle()
// Signal to the system that the initial UI is "fully drawn"
// once we've decided whether to show the intro or the main screen.
ReportDrawnWhen { true }
if (appIntroCompleted) {
MainScreen(uIViewModel = model)
} else {
AppIntroductionScreen(onDone = { model.onAppIntroCompleted() })
}
}
}
}
if (savedInstanceState == null) {
handleIntent(intent)
}
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
// Listen for new intents (e.g. deep links, NFC) without overriding onNewIntent
addOnNewIntentListener { intent -> handleIntent(intent) }
handleIntent(intent)
}
@ -125,7 +131,12 @@ class MainActivity : AppCompatActivity() {
}
NfcAdapter.ACTION_NDEF_DISCOVERED -> {
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
val rawMessages =
IntentCompat.getParcelableArrayExtra(
intent,
NfcAdapter.EXTRA_NDEF_MESSAGES,
NdefMessage::class.java,
)
if (rawMessages != null) {
for (rawMsg in rawMessages) {
val msg = rawMsg as NdefMessage