mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: Check if NFC is enabled and prompt user to enable it (#4482)
This commit is contained in:
parent
edd658f063
commit
1e255a5120
6 changed files with 111 additions and 21 deletions
|
|
@ -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 com.geeksville.mesh
|
||||
|
||||
import android.app.PendingIntent
|
||||
|
|
@ -23,6 +22,8 @@ import android.content.Intent
|
|||
import android.graphics.Color
|
||||
import android.hardware.usb.UsbManager
|
||||
import android.net.Uri
|
||||
import android.nfc.NdefMessage
|
||||
import android.nfc.NfcAdapter
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.SystemBarStyle
|
||||
|
|
@ -46,6 +47,7 @@ import com.geeksville.mesh.ui.MainScreen
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.datastore.UiPreferencesDataSource
|
||||
import org.meshtastic.core.model.util.handleMeshtasticUri
|
||||
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.channel_invalid
|
||||
|
|
@ -111,28 +113,24 @@ class MainActivity : AppCompatActivity() {
|
|||
handleIntent(intent)
|
||||
}
|
||||
|
||||
@Suppress("NestedBlockDepth")
|
||||
private fun handleIntent(intent: Intent) {
|
||||
val appLinkAction = intent.action
|
||||
val appLinkData: Uri? = intent.data
|
||||
|
||||
when (appLinkAction) {
|
||||
Intent.ACTION_VIEW -> {
|
||||
appLinkData?.let {
|
||||
Logger.d { "App link data: $it" }
|
||||
if (it.path?.startsWith("/e/") == true || it.path?.startsWith("/E/") == true) {
|
||||
Logger.d { "App link data is a channel set" }
|
||||
model.requestChannelUrl(
|
||||
url = it,
|
||||
onFailure = { lifecycleScope.launch { showToast(Res.string.channel_invalid) } },
|
||||
)
|
||||
} else if (it.path?.startsWith("/v/") == true || it.path?.startsWith("/V/") == true) {
|
||||
Logger.d { "App link data is a shared contact" }
|
||||
model.setSharedContactRequested(
|
||||
url = it,
|
||||
onFailure = { lifecycleScope.launch { showToast(Res.string.contact_invalid) } },
|
||||
)
|
||||
} else {
|
||||
Logger.d { "App link data is not a channel set" }
|
||||
appLinkData?.let { handleMeshtasticUri(it) }
|
||||
}
|
||||
|
||||
NfcAdapter.ACTION_NDEF_DISCOVERED -> {
|
||||
val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
|
||||
if (rawMessages != null) {
|
||||
for (rawMsg in rawMessages) {
|
||||
val msg = rawMsg as NdefMessage
|
||||
for (record in msg.records) {
|
||||
record.toUri()?.let { handleMeshtasticUri(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,6 +155,25 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleMeshtasticUri(uri: Uri) {
|
||||
Logger.d { "Handling Meshtastic URI: $uri" }
|
||||
handleMeshtasticUri(
|
||||
uri = uri,
|
||||
onChannel = {
|
||||
model.requestChannelUrl(
|
||||
url = it,
|
||||
onFailure = { lifecycleScope.launch { showToast(Res.string.channel_invalid) } },
|
||||
)
|
||||
},
|
||||
onContact = {
|
||||
model.setSharedContactRequested(
|
||||
url = it,
|
||||
onFailure = { lifecycleScope.launch { showToast(Res.string.contact_invalid) } },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun createShareIntent(message: String): PendingIntent {
|
||||
val deepLink = "$DEEP_LINK_BASE_URI/share?message=$message"
|
||||
val startActivityIntent =
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ import co.touchlab.kermit.Logger
|
|||
import java.io.IOException
|
||||
|
||||
@Composable
|
||||
fun NfcScannerEffect(onResult: (String?) -> Unit) {
|
||||
fun NfcScannerEffect(onResult: (String?) -> Unit, onNfcDisabled: (() -> Unit)? = null) {
|
||||
val context = LocalContext.current
|
||||
val activity = context as? Activity ?: return
|
||||
|
||||
|
|
@ -37,6 +37,9 @@ fun NfcScannerEffect(onResult: (String?) -> Unit) {
|
|||
DisposableEffect(nfcAdapter) {
|
||||
if (nfcAdapter == null) {
|
||||
onDispose {}
|
||||
} else if (!nfcAdapter.isEnabled) {
|
||||
onNfcDisabled?.invoke()
|
||||
onDispose {}
|
||||
} else {
|
||||
val readerCallback = NfcAdapter.ReaderCallback { tag: Tag -> handleNfcTag(tag, onResult) }
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. See <https://www.gnu.org/licenses/>.
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
|
|
@ -1179,4 +1179,5 @@
|
|||
<string name="share_channels_qr">Share Channels QR Code</string>
|
||||
<string name="scan_nfc_text">Bring your device close to the NFC tag to scan.</string>
|
||||
<string name="generate_qr_code">Generate QR Code</string>
|
||||
<string name="nfc_disabled">NFC is disabled. Please enable it in system settings.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
|
@ -41,7 +42,9 @@ import org.meshtastic.core.nfc.NfcScannerEffect
|
|||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.input_shared_contact_url
|
||||
import org.meshtastic.core.strings.nfc_disabled
|
||||
import org.meshtastic.core.strings.okay
|
||||
import org.meshtastic.core.strings.open_settings
|
||||
import org.meshtastic.core.strings.scan_channels_nfc
|
||||
import org.meshtastic.core.strings.scan_channels_qr
|
||||
import org.meshtastic.core.strings.scan_nfc
|
||||
|
|
@ -52,6 +55,7 @@ import org.meshtastic.core.strings.share_channels_qr
|
|||
import org.meshtastic.core.strings.url
|
||||
import org.meshtastic.core.ui.icon.MeshtasticIcons
|
||||
import org.meshtastic.core.ui.icon.QrCode2
|
||||
import org.meshtastic.core.ui.util.openNfcSettings
|
||||
|
||||
/**
|
||||
* Unified Floating Action Button for importing Meshtastic data (Contacts, Channels, etc.) via NFC, QR, or URL.
|
||||
|
|
@ -72,6 +76,8 @@ fun ImportFab(
|
|||
var expanded by remember { mutableStateOf(false) }
|
||||
var showUrlDialog by remember { mutableStateOf(false) }
|
||||
var isNfcScanning by remember { mutableStateOf(false) }
|
||||
var showNfcDisabledDialog by remember { mutableStateOf(false) }
|
||||
val context = LocalContext.current
|
||||
|
||||
val barcodeScanner =
|
||||
rememberBarcodeScanner(
|
||||
|
|
@ -91,10 +97,35 @@ fun ImportFab(
|
|||
isNfcScanning = false
|
||||
}
|
||||
},
|
||||
onNfcDisabled = {
|
||||
isNfcScanning = false
|
||||
showNfcDisabledDialog = true
|
||||
},
|
||||
)
|
||||
NfcScanningDialog(onDismiss = { isNfcScanning = false })
|
||||
}
|
||||
|
||||
if (showNfcDisabledDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showNfcDisabledDialog = false },
|
||||
title = { Text(stringResource(Res.string.scan_nfc)) },
|
||||
text = { Text(stringResource(Res.string.nfc_disabled)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
context.openNfcSettings()
|
||||
showNfcDisabledDialog = false
|
||||
},
|
||||
) {
|
||||
Text(stringResource(Res.string.open_settings))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showNfcDisabledDialog = false }) { Text(stringResource(Res.string.cancel)) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (showUrlDialog) {
|
||||
InputUrlDialog(
|
||||
title = stringResource(Res.string.input_shared_contact_url),
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ package org.meshtastic.core.ui.util
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.Intent
|
||||
import android.provider.Settings
|
||||
import android.widget.Toast
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
|
@ -41,3 +43,8 @@ fun Context.findActivity(): Activity? = when (this) {
|
|||
is ContextWrapper -> baseContext.findActivity()
|
||||
else -> null
|
||||
}
|
||||
|
||||
fun Context.openNfcSettings() {
|
||||
val intent = Intent(Settings.ACTION_NFC_SETTINGS)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,19 @@ import androidx.compose.foundation.layout.height
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
|
|
@ -45,6 +48,7 @@ import org.meshtastic.core.model.util.handleMeshtasticUri
|
|||
import org.meshtastic.core.nfc.NfcScannerEffect
|
||||
import org.meshtastic.core.strings.Res
|
||||
import org.meshtastic.core.strings.advanced
|
||||
import org.meshtastic.core.strings.cancel
|
||||
import org.meshtastic.core.strings.config_network_eth_enabled_summary
|
||||
import org.meshtastic.core.strings.config_network_udp_enabled_summary
|
||||
import org.meshtastic.core.strings.config_network_wifi_enabled_summary
|
||||
|
|
@ -57,9 +61,12 @@ import org.meshtastic.core.strings.gateway
|
|||
import org.meshtastic.core.strings.ip
|
||||
import org.meshtastic.core.strings.ipv4_mode
|
||||
import org.meshtastic.core.strings.network
|
||||
import org.meshtastic.core.strings.nfc_disabled
|
||||
import org.meshtastic.core.strings.ntp_server
|
||||
import org.meshtastic.core.strings.open_settings
|
||||
import org.meshtastic.core.strings.password
|
||||
import org.meshtastic.core.strings.rsyslog_server
|
||||
import org.meshtastic.core.strings.scan_nfc
|
||||
import org.meshtastic.core.strings.ssid
|
||||
import org.meshtastic.core.strings.subnet
|
||||
import org.meshtastic.core.strings.udp_enabled
|
||||
|
|
@ -76,6 +83,7 @@ import org.meshtastic.core.ui.component.ListItem
|
|||
import org.meshtastic.core.ui.component.SimpleAlertDialog
|
||||
import org.meshtastic.core.ui.component.SwitchPreference
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.util.openNfcSettings
|
||||
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
|
||||
import org.meshtastic.proto.Config
|
||||
|
||||
|
|
@ -88,12 +96,35 @@ fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
|
|||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val networkConfig = state.radioConfig.network ?: Config.NetworkConfig()
|
||||
val formState = rememberConfigState(initialValue = networkConfig)
|
||||
val context = LocalContext.current
|
||||
|
||||
var showScanErrorDialog: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
if (showScanErrorDialog) {
|
||||
ScanErrorDialog { showScanErrorDialog = false }
|
||||
}
|
||||
|
||||
var showNfcDisabledDialog: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
if (showNfcDisabledDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showNfcDisabledDialog = false },
|
||||
title = { Text(stringResource(Res.string.scan_nfc)) },
|
||||
text = { Text(stringResource(Res.string.nfc_disabled)) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
context.openNfcSettings()
|
||||
showNfcDisabledDialog = false
|
||||
},
|
||||
) {
|
||||
Text(stringResource(Res.string.open_settings))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showNfcDisabledDialog = false }) { Text(stringResource(Res.string.cancel)) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
val onResult: (String?) -> Unit = { contents ->
|
||||
if (contents != null) {
|
||||
val handled =
|
||||
|
|
@ -115,7 +146,7 @@ fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
|
|||
}
|
||||
|
||||
val barcodeScanner = rememberBarcodeScanner(onResult = onResult)
|
||||
NfcScannerEffect(onResult = onResult)
|
||||
NfcScannerEffect(onResult = onResult, onNfcDisabled = { showNfcDisabledDialog = true })
|
||||
|
||||
val focusManager = LocalFocusManager.current
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue