mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: consolidate dialogs (#4506)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
7bcc51863f
commit
ea6d1ffa32
59 changed files with 2042 additions and 1659 deletions
|
|
@ -39,7 +39,13 @@ private const val BASE64FLAGS = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PAD
|
|||
*/
|
||||
@Throws(MalformedURLException::class)
|
||||
fun Uri.toChannelSet(): ChannelSet {
|
||||
if (fragment.isNullOrBlank() || !host.equals(MESHTASTIC_HOST, true) || !path.equals(CHANNEL_SHARE_PATH, true)) {
|
||||
val h = host ?: ""
|
||||
val isCorrectHost =
|
||||
h.equals(MESHTASTIC_HOST, ignoreCase = true) || h.equals("www.$MESHTASTIC_HOST", ignoreCase = true)
|
||||
val segments = pathSegments
|
||||
val isCorrectPath = segments.any { it.equals("e", ignoreCase = true) }
|
||||
|
||||
if (fragment.isNullOrBlank() || !isCorrectHost || !isCorrectPath) {
|
||||
throw MalformedURLException("Not a valid Meshtastic URL: ${toString().take(40)}")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* 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.model.util
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Base64
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.meshtastic.proto.SharedContact
|
||||
import org.meshtastic.proto.User
|
||||
import java.net.MalformedURLException
|
||||
|
||||
private const val BASE64FLAGS = Base64.URL_SAFE + Base64.NO_WRAP + Base64.NO_PADDING
|
||||
|
||||
/**
|
||||
* Return a [SharedContact] that represents the contact encoded by the URL.
|
||||
*
|
||||
* @throws MalformedURLException when not recognized as a valid Meshtastic URL
|
||||
*/
|
||||
@Throws(MalformedURLException::class)
|
||||
fun Uri.toSharedContact(): SharedContact {
|
||||
val h = host ?: ""
|
||||
val isCorrectHost =
|
||||
h.equals(MESHTASTIC_HOST, ignoreCase = true) || h.equals("www.$MESHTASTIC_HOST", ignoreCase = true)
|
||||
val segments = pathSegments
|
||||
val isCorrectPath = segments.any { it.equals("v", ignoreCase = true) }
|
||||
|
||||
if (fragment.isNullOrBlank() || !isCorrectHost || !isCorrectPath) {
|
||||
throw MalformedURLException("Not a valid Meshtastic URL")
|
||||
}
|
||||
return SharedContact.ADAPTER.decode(Base64.decode(fragment!!, BASE64FLAGS).toByteString())
|
||||
}
|
||||
|
||||
/** Converts a [SharedContact] to its corresponding URI representation. */
|
||||
fun SharedContact.getSharedContactUrl(): Uri {
|
||||
val bytes = SharedContact.ADAPTER.encode(this)
|
||||
val enc = Base64.encodeToString(bytes, BASE64FLAGS)
|
||||
return Uri.parse("$CONTACT_URL_PREFIX$enc")
|
||||
}
|
||||
|
||||
/** Compares two [User] objects and returns a string detailing the differences. */
|
||||
fun compareUsers(oldUser: User, newUser: User): String {
|
||||
val changes = mutableListOf<String>()
|
||||
|
||||
if (oldUser.id != newUser.id) changes.add("id: ${oldUser.id} -> ${newUser.id}")
|
||||
if (oldUser.long_name != newUser.long_name) changes.add("long_name: ${oldUser.long_name} -> ${newUser.long_name}")
|
||||
if (oldUser.short_name != newUser.short_name) {
|
||||
changes.add("short_name: ${oldUser.short_name} -> ${newUser.short_name}")
|
||||
}
|
||||
@Suppress("DEPRECATION")
|
||||
if (oldUser.macaddr != newUser.macaddr) {
|
||||
changes.add("macaddr: ${oldUser.macaddr.base64String()} -> ${newUser.macaddr.base64String()}")
|
||||
}
|
||||
if (oldUser.hw_model != newUser.hw_model) changes.add("hw_model: ${oldUser.hw_model} -> ${newUser.hw_model}")
|
||||
if (oldUser.is_licensed != newUser.is_licensed) {
|
||||
changes.add("is_licensed: ${oldUser.is_licensed} -> ${newUser.is_licensed}")
|
||||
}
|
||||
if (oldUser.role != newUser.role) changes.add("role: ${oldUser.role} -> ${newUser.role}")
|
||||
if (oldUser.public_key != newUser.public_key) {
|
||||
changes.add("public_key: ${oldUser.public_key.base64String()} -> ${newUser.public_key.base64String()}")
|
||||
}
|
||||
|
||||
return if (changes.isEmpty()) {
|
||||
"No changes detected."
|
||||
} else {
|
||||
"Changes:\n" + changes.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
/** Converts a [User] object to a string representation of its fields and values. */
|
||||
fun userFieldsToString(user: User): String {
|
||||
val fieldLines = mutableListOf<String>()
|
||||
|
||||
fieldLines.add("id: ${user.id}")
|
||||
fieldLines.add("long_name: ${user.long_name}")
|
||||
fieldLines.add("short_name: ${user.short_name}")
|
||||
@Suppress("DEPRECATION")
|
||||
fieldLines.add("macaddr: ${user.macaddr.base64String()}")
|
||||
fieldLines.add("hw_model: ${user.hw_model}")
|
||||
fieldLines.add("is_licensed: ${user.is_licensed}")
|
||||
fieldLines.add("role: ${user.role}")
|
||||
fieldLines.add("public_key: ${user.public_key.base64String()}")
|
||||
|
||||
return fieldLines.joinToString("\n")
|
||||
}
|
||||
|
||||
private fun ByteString.base64String(): String = Base64.encodeToString(this.toByteArray(), Base64.DEFAULT).trim()
|
||||
|
|
@ -17,31 +17,75 @@
|
|||
package org.meshtastic.core.model.util
|
||||
|
||||
import android.net.Uri
|
||||
import co.touchlab.kermit.Logger
|
||||
import org.meshtastic.proto.ChannelSet
|
||||
import org.meshtastic.proto.SharedContact
|
||||
|
||||
/**
|
||||
* Dispatches an incoming Meshtastic URI to the appropriate handler.
|
||||
* Dispatches an incoming Meshtastic URI to the appropriate handler based on its path.
|
||||
*
|
||||
* @param uri The URI to handle.
|
||||
* @param onChannel Callback if the URI is a Channel Set (path starts with /e/).
|
||||
* @param onContact Callback if the URI is a Shared Contact (path starts with /v/).
|
||||
* @param onChannel Callback if the URI is a Channel Set.
|
||||
* @param onContact Callback if the URI is a Shared Contact.
|
||||
* @return True if the URI was handled (matched a supported path), false otherwise.
|
||||
*/
|
||||
fun handleMeshtasticUri(uri: Uri, onChannel: (Uri) -> Unit = {}, onContact: (Uri) -> Unit = {}): Boolean {
|
||||
val path = uri.path
|
||||
// Only handle meshtastic.org URLs
|
||||
if (uri.host?.equals(MESHTASTIC_HOST, ignoreCase = true) != true || path == null) {
|
||||
return false
|
||||
}
|
||||
val h = uri.host ?: ""
|
||||
val isCorrectHost =
|
||||
h.equals(MESHTASTIC_HOST, ignoreCase = true) || h.equals("www.$MESHTASTIC_HOST", ignoreCase = true)
|
||||
if (!isCorrectHost) return false
|
||||
|
||||
val segments = uri.pathSegments
|
||||
return when {
|
||||
path.startsWith(CHANNEL_SHARE_PATH, ignoreCase = true) -> {
|
||||
segments.any { it.equals("e", ignoreCase = true) } -> {
|
||||
onChannel(uri)
|
||||
true
|
||||
}
|
||||
path.startsWith(CONTACT_SHARE_PATH, ignoreCase = true) -> {
|
||||
segments.any { it.equals("v", ignoreCase = true) } -> {
|
||||
onContact(uri)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to parse a Meshtastic URI as a Channel Set or Shared Contact, including fallback logic.
|
||||
*
|
||||
* @param onChannel Callback when successfully parsed as a [ChannelSet].
|
||||
* @param onContact Callback when successfully parsed as a [SharedContact].
|
||||
* @param onInvalid Callback when parsing fails or the URI is not a Meshtastic URL.
|
||||
*/
|
||||
fun Uri.dispatchMeshtasticUri(
|
||||
onChannel: (ChannelSet) -> Unit,
|
||||
onContact: (SharedContact) -> Unit,
|
||||
onInvalid: () -> Unit,
|
||||
) {
|
||||
val handled =
|
||||
handleMeshtasticUri(
|
||||
uri = this,
|
||||
onChannel = { u ->
|
||||
runCatching { u.toChannelSet() }
|
||||
.onSuccess(onChannel)
|
||||
.onFailure { ex ->
|
||||
Logger.e(ex) { "Channel parsing error" }
|
||||
onInvalid()
|
||||
}
|
||||
},
|
||||
onContact = { u ->
|
||||
runCatching { u.toSharedContact() }
|
||||
.onSuccess(onContact)
|
||||
.onFailure { ex ->
|
||||
Logger.e(ex) { "Contact parsing error" }
|
||||
onInvalid()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
if (!handled) {
|
||||
// Fallback: try as contact first, then as channel
|
||||
runCatching { toSharedContact() }
|
||||
.onSuccess(onContact)
|
||||
.onFailure { runCatching { toChannelSet() }.onSuccess(onChannel).onFailure { onInvalid() } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue