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

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.component
import android.text.Spannable
@ -32,6 +31,7 @@ import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextLinkStyles
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withLink
import androidx.compose.ui.tooling.preview.Preview
@ -48,9 +48,15 @@ fun AutoLinkText(
style: TextStyle = TextStyle.Default,
linkStyles: TextLinkStyles = DefaultTextLinkStyles,
color: Color = Color.Unspecified,
textAlign: TextAlign? = null,
) {
val spannable = remember(text) { linkify(text) }
Text(text = spannable.toAnnotatedString(linkStyles), modifier = modifier, style = style.copy(color = color))
Text(
text = spannable.toAnnotatedString(linkStyles),
modifier = modifier,
style = style.copy(color = color),
textAlign = textAlign,
)
}
private fun linkify(text: String) = Factory.getInstance().newSpannable(text).also {

View file

@ -24,7 +24,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.nowSeconds
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.node_sort_last_heard
import org.meshtastic.core.ui.R

View file

@ -16,8 +16,6 @@
*/
package org.meshtastic.core.ui.component
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.compose.runtime.Composable
@ -25,45 +23,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.LifecycleResumeEffect
import org.meshtastic.core.model.util.nowMillis
import no.nordicsemi.android.common.core.registerReceiver
/**
* Remembers a time tick that updates every minute. Uses [registerReceiver] from Nordic Common for automatic lifecycle
* management.
*
* @return The current time in milliseconds, updating every minute.
*/
@Composable
fun rememberTimeTickWithLifecycle(): Long {
val context = LocalContext.current
var value by remember { mutableLongStateOf(nowMillis) }
val receiver = TimeBroadcastReceiver { value = nowMillis }
var value by remember { mutableLongStateOf(System.currentTimeMillis()) }
LifecycleResumeEffect(Unit) {
receiver.register(context)
value = nowMillis
onPauseOrDispose { receiver.unregister(context) }
}
registerReceiver(IntentFilter(Intent.ACTION_TIME_TICK)) { value = System.currentTimeMillis() }
return value
}
private class TimeBroadcastReceiver(val onTimeChanged: () -> Unit) : BroadcastReceiver() {
private var registered = false
override fun onReceive(context: Context, intent: Intent) {
onTimeChanged()
}
fun register(context: Context) {
if (!registered) {
val filter = IntentFilter(Intent.ACTION_TIME_TICK)
context.registerReceiver(this, filter)
registered = true
}
}
fun unregister(context: Context) {
if (registered) {
context.unregisterReceiver(this)
registered = false
}
}
}

View file

@ -17,9 +17,9 @@
package org.meshtastic.core.ui.util
import android.text.format.DateUtils
import com.meshtastic.core.strings.getString
import org.meshtastic.core.model.util.nowMillis
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.getString
import org.meshtastic.core.strings.now
import org.meshtastic.core.strings.unknown
import kotlin.time.Duration.Companion.milliseconds

View file

@ -14,18 +14,13 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.ui.timezone
package org.meshtastic.core.ui.util
import org.meshtastic.core.model.util.toPosixString
import java.time.ZoneId
import androidx.compose.ui.Modifier
/**
* Generates a POSIX time zone string from a [ZoneId].
*
* @deprecated Use [org.meshtastic.core.model.util.toPosixString] instead.
* Conditionally applies the [action] to the receiver [Modifier] if [precondition] is true. Otherwise, returns the
* receiver unchanged.
*/
@Deprecated(
message = "Use org.meshtastic.core.model.util.toPosixString instead",
replaceWith = ReplaceWith("this.toPosixString()", "org.meshtastic.core.model.util.toPosixString"),
)
fun ZoneId.toPosixString(): String = this.toPosixString()
inline fun Modifier.thenIf(precondition: Boolean, action: Modifier.() -> Modifier): Modifier =
if (precondition) action() else this

View file

@ -20,7 +20,7 @@ import android.text.format.DateUtils
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.nowMillis
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.unknown_age
import org.meshtastic.proto.Channel