From 40a142064f655bf90d53985b900a638d2be3437b Mon Sep 17 00:00:00 2001 From: geeksville Date: Tue, 17 Mar 2020 11:35:19 -0700 Subject: [PATCH] channel sharing WIP --- .../java/com/geeksville/mesh/MainActivity.kt | 12 ++- .../java/com/geeksville/mesh/model/UIState.kt | 76 +++++++++++-------- .../java/com/geeksville/mesh/ui/Channel.kt | 7 +- .../com/geeksville/mesh/model/ChannelTest.kt | 14 ++++ 4 files changed, 70 insertions(+), 39 deletions(-) create mode 100644 app/src/test/java/com/geeksville/mesh/model/ChannelTest.kt diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index dd816aca1..0bf71ffdb 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -200,9 +200,6 @@ class MainActivity : AppCompatActivity(), Logging, requestPermission() - setContent { - MeshApp() - } /* not yet working // Configure sign-in to request the user's ID, email address, and basic @@ -221,6 +218,10 @@ class MainActivity : AppCompatActivity(), Logging, // Handle any intent handleIntent(intent) + + setContent { + MeshApp() + } } override fun onNewIntent(intent: Intent) { @@ -233,9 +234,12 @@ class MainActivity : AppCompatActivity(), Logging, val appLinkAction = intent.action val appLinkData: Uri? = intent.data + UIState.requestedChannelUrl = null // assume none + // Were we asked to open one our channel URLs? - if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) { + if (Intent.ACTION_VIEW == appLinkAction) { debug("Asked to open a channel URL - FIXME, ask user if they want to switch to that channel. If so send the config to the radio") + UIState.requestedChannelUrl = appLinkData } } diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index fe54d37ea..c730cec60 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -3,6 +3,7 @@ package com.geeksville.mesh.model import android.content.Context import android.content.SharedPreferences import android.graphics.Bitmap +import android.net.Uri import android.os.RemoteException import android.util.Base64 import androidx.compose.Model @@ -17,24 +18,61 @@ import com.geeksville.mesh.ui.getInitials import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter import com.journeyapps.barcodescanner.BarcodeEncoder +import java.net.MalformedURLException @Model data class Channel( var name: String, - var modemConfig: ModemConfig + var modemConfig: ModemConfig, + var settings: MeshProtos.ChannelSettings? = null ) { companion object { // Placeholder when emulating val emulated = Channel("Default", ModemConfig.Bw125Cr45Sf128) + + const val prefix = "https://www.meshtastic.org/c/" + + private const val base64Flags = Base64.URL_SAFE + Base64.NO_WRAP + + private fun urlToSettings(url: Uri): MeshProtos.ChannelSettings { + val urlStr = url.toString() + val pathRegex = Regex("$prefix(.*)") + val (base64) = pathRegex.find(urlStr)?.destructured + ?: throw MalformedURLException("Not a meshtastic URL") + val bytes = Base64.decode(base64, base64Flags) + + return MeshProtos.ChannelSettings.parseFrom(bytes) + } } - constructor(c: MeshProtos.ChannelSettings) : this(c.name, c.modemConfig) { - } + constructor(c: MeshProtos.ChannelSettings) : this(c.name, c.modemConfig, c) + + constructor(url: Uri) : this(urlToSettings(url)) /// Can this channel be changed right now? var editable = false + + /// Return an URL that represents the current channel values + fun getChannelUrl(): String { + // If we have a valid radio config use it, othterwise use whatever we have saved in the prefs + + val channelBytes = settings?.toByteArray() ?: ByteArray(0) // if unset just use empty + val enc = Base64.encodeToString(channelBytes, base64Flags) + + return "$prefix$enc" + } + + fun getChannelQR(): Bitmap { + val multiFormatWriter = MultiFormatWriter() + + val bitMatrix = + multiFormatWriter.encode(getChannelUrl(), BarcodeFormat.QR_CODE, 192, 192); + val barcodeEncoder = BarcodeEncoder() + return barcodeEncoder.createBitmap(bitMatrix) + } } + /** * a nice readable description of modem configs */ @@ -65,6 +103,9 @@ object UIState : Logging { /// our activity will read this from prefs or set it to the empty string var ownerName: String = "MrInIDE Ownername" + /// If the app was launched because we received a new channel intent, the Url will be here + var requestedChannelUrl: Uri? = null + /** * Return the current channel info * FIXME, we should sim channels at the MeshService level if we are running on an emulator, @@ -79,33 +120,6 @@ object UIState : Logging { channel } - /// Return an URL that represents the current channel values - fun getChannelUrl(context: Context): String { - // If we have a valid radio config use it, othterwise use whatever we have saved in the prefs - val radio = radioConfig.value - if (radio != null) { - val settings = radio.channelSettings - val channelBytes = settings.toByteArray() - val enc = Base64.encodeToString(channelBytes, Base64.URL_SAFE + Base64.NO_WRAP) - - return "https://www.meshtastic.org/c/$enc" - } else { - return getPreferences(context).getString( - "owner", - "https://www.meshtastic.org/c/unset" - )!! - } - } - - fun getChannelQR(context: Context): Bitmap { - val multiFormatWriter = MultiFormatWriter() - - val bitMatrix = - multiFormatWriter.encode(getChannelUrl(context), BarcodeFormat.QR_CODE, 192, 192); - val barcodeEncoder = BarcodeEncoder() - return barcodeEncoder.createBitmap(bitMatrix) - } - fun getPreferences(context: Context): SharedPreferences = context.getSharedPreferences("ui-prefs", Context.MODE_PRIVATE) @@ -114,7 +128,7 @@ object UIState : Logging { radioConfig.value = c getPreferences(context).edit(commit = true) { - this.putString("channel-url", getChannelUrl(context)) + this.putString("channel-url", getChannel()!!.getChannelUrl()) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/Channel.kt b/app/src/main/java/com/geeksville/mesh/ui/Channel.kt index a55a5c5bf..63c0355cb 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Channel.kt @@ -15,7 +15,6 @@ import com.geeksville.android.GeeksvilleApplication import com.geeksville.android.Logging import com.geeksville.mesh.R import com.geeksville.mesh.model.Channel -import com.geeksville.mesh.model.UIState import com.geeksville.mesh.model.toHumanString @@ -43,7 +42,7 @@ fun ChannelContent(channel: Channel?) { textStyle = typography.h4.copy( color = palette.onSecondary.copy(alpha = 0.8f) ), - imeAction = ImeAction.Send, + imeAction = ImeAction.Done, onImeActionPerformed = { TODO() } @@ -58,7 +57,7 @@ fun ChannelContent(channel: Channel?) { // simulated qr code // val image = imageResource(id = R.drawable.qrcode) - val image = AndroidImage(UIState.getChannelQR(context)) + val image = AndroidImage(channel.getChannelQR()) ScaledImage( image = image, @@ -98,7 +97,7 @@ fun ChannelContent(channel: Channel?) { val sendIntent: Intent = Intent().apply { action = Intent.ACTION_SEND - putExtra(Intent.EXTRA_TEXT, UIState.getChannelUrl(context)) + putExtra(Intent.EXTRA_TEXT, channel.getChannelUrl()) putExtra( Intent.EXTRA_TITLE, "A URL for joining a Meshtastic mesh" diff --git a/app/src/test/java/com/geeksville/mesh/model/ChannelTest.kt b/app/src/test/java/com/geeksville/mesh/model/ChannelTest.kt new file mode 100644 index 000000000..147086f60 --- /dev/null +++ b/app/src/test/java/com/geeksville/mesh/model/ChannelTest.kt @@ -0,0 +1,14 @@ +package com.geeksville.mesh.model + +import org.junit.Test + +class ChannelTest { + @Test + fun channelUrlGood() { + val ch = Channel.emulated + + // FIXME, currently not allowed because it is a Compose model + // Assert.assertTrue(ch.getChannelUrl().startsWith(Channel.prefix)) + } + +} \ No newline at end of file