kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
showing real channel data works
rodzic
6ce859a952
commit
36b2da72e4
|
@ -10,11 +10,37 @@ import androidx.core.content.edit
|
||||||
import com.geeksville.android.Logging
|
import com.geeksville.android.Logging
|
||||||
import com.geeksville.mesh.IMeshService
|
import com.geeksville.mesh.IMeshService
|
||||||
import com.geeksville.mesh.MeshProtos
|
import com.geeksville.mesh.MeshProtos
|
||||||
|
import com.geeksville.mesh.MeshProtos.ChannelSettings.ModemConfig
|
||||||
import com.geeksville.mesh.ui.getInitials
|
import com.geeksville.mesh.ui.getInitials
|
||||||
import com.google.zxing.BarcodeFormat
|
import com.google.zxing.BarcodeFormat
|
||||||
import com.google.zxing.MultiFormatWriter
|
import com.google.zxing.MultiFormatWriter
|
||||||
import com.journeyapps.barcodescanner.BarcodeEncoder
|
import com.journeyapps.barcodescanner.BarcodeEncoder
|
||||||
|
|
||||||
|
data class Channel(
|
||||||
|
val name: String,
|
||||||
|
val num: Int,
|
||||||
|
val modemConfig: ModemConfig = ModemConfig.Bw125Cr45Sf128
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
// Placeholder when emulating
|
||||||
|
val emulated = Channel("Default", 7)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(c: MeshProtos.ChannelSettings) : this(c.name, c.channelNum, c.modemConfig) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a nice readable description of modem configs
|
||||||
|
*/
|
||||||
|
fun ModemConfig.toHumanString(): String = when (this) {
|
||||||
|
ModemConfig.Bw125Cr45Sf128 -> "Medium range (but fast)"
|
||||||
|
ModemConfig.Bw500Cr45Sf128 -> "Short range (but fast)"
|
||||||
|
ModemConfig.Bw31_25Cr48Sf512 -> "Long range (but slower)"
|
||||||
|
ModemConfig.Bw125Cr48Sf4096 -> "Very long range (but slow)"
|
||||||
|
else -> this.toString()
|
||||||
|
}
|
||||||
|
|
||||||
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
/// FIXME - figure out how to merge this staate with the AppStatus Model
|
||||||
object UIState : Logging {
|
object UIState : Logging {
|
||||||
|
|
||||||
|
@ -34,6 +60,8 @@ object UIState : Logging {
|
||||||
/// our activity will read this from prefs or set it to the empty string
|
/// our activity will read this from prefs or set it to the empty string
|
||||||
var ownerName: String = "MrInIDE Ownername"
|
var ownerName: String = "MrInIDE Ownername"
|
||||||
|
|
||||||
|
fun getChannel() = radioConfig.value?.channelSettings?.let { Channel(it) }
|
||||||
|
|
||||||
/// Return an URL that represents the current channel values
|
/// Return an URL that represents the current channel values
|
||||||
fun getChannelUrl(context: Context): String {
|
fun getChannelUrl(context: Context): String {
|
||||||
// If we have a valid radio config use it, othterwise use whatever we have saved in the prefs
|
// If we have a valid radio config use it, othterwise use whatever we have saved in the prefs
|
||||||
|
@ -52,8 +80,7 @@ object UIState : Logging {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getChannelQR(context: Context): Bitmap
|
fun getChannelQR(context: Context): Bitmap {
|
||||||
{
|
|
||||||
val multiFormatWriter = MultiFormatWriter()
|
val multiFormatWriter = MultiFormatWriter()
|
||||||
|
|
||||||
val bitMatrix =
|
val bitMatrix =
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
package com.geeksville.mesh.ui
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import androidx.compose.Composable
|
||||||
|
import androidx.ui.core.DensityAmbient
|
||||||
|
import androidx.ui.core.DrawModifier
|
||||||
|
import androidx.ui.core.Modifier
|
||||||
|
import androidx.ui.core.toModifier
|
||||||
|
import androidx.ui.foundation.Box
|
||||||
|
import androidx.ui.graphics.*
|
||||||
|
import androidx.ui.graphics.colorspace.ColorSpace
|
||||||
|
import androidx.ui.graphics.colorspace.ColorSpaces
|
||||||
|
import androidx.ui.graphics.painter.ImagePainter
|
||||||
|
import androidx.ui.unit.Density
|
||||||
|
import androidx.ui.unit.PxSize
|
||||||
|
import androidx.ui.unit.toRect
|
||||||
|
|
||||||
|
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
||||||
|
// TODO(mount, malkov) : remove when RepaintBoundary is a modifier: b/149982905
|
||||||
|
// This is class and not val because if b/149985596
|
||||||
|
private object ClipModifier : DrawModifier {
|
||||||
|
override fun draw(density: Density, drawContent: () -> Unit, canvas: Canvas, size: PxSize) {
|
||||||
|
canvas.save()
|
||||||
|
canvas.clipRect(size.toRect())
|
||||||
|
drawContent()
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
||||||
|
@Composable
|
||||||
|
fun ScaledImage(
|
||||||
|
image: Image,
|
||||||
|
modifier: Modifier = Modifier.None,
|
||||||
|
tint: Color? = null
|
||||||
|
) {
|
||||||
|
with(DensityAmbient.current) {
|
||||||
|
val imageModifier = ImagePainter(image).toModifier(
|
||||||
|
scaleFit = ScaleFit.FillMaxDimension,
|
||||||
|
colorFilter = tint?.let { ColorFilter(it, BlendMode.srcIn) }
|
||||||
|
)
|
||||||
|
Box(modifier + ClipModifier + imageModifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Borrowed from Compose
|
||||||
|
class AndroidImage(val bitmap: Bitmap) : Image {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Image.width
|
||||||
|
*/
|
||||||
|
override val width: Int
|
||||||
|
get() = bitmap.width
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Image.height
|
||||||
|
*/
|
||||||
|
override val height: Int
|
||||||
|
get() = bitmap.height
|
||||||
|
|
||||||
|
override val config: ImageConfig get() = ImageConfig.Argb8888
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Image.colorSpace
|
||||||
|
*/
|
||||||
|
override val colorSpace: ColorSpace
|
||||||
|
get() = ColorSpaces.Srgb
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Image.hasAlpha
|
||||||
|
*/
|
||||||
|
override val hasAlpha: Boolean
|
||||||
|
get() = bitmap.hasAlpha()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see Image.nativeImage
|
||||||
|
*/
|
||||||
|
override val nativeImage: NativeImage
|
||||||
|
get() = bitmap
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see
|
||||||
|
*/
|
||||||
|
override fun prepareToDraw() {
|
||||||
|
bitmap.prepareToDraw()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,164 +1,87 @@
|
||||||
package com.geeksville.mesh.ui
|
package com.geeksville.mesh.ui
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import androidx.compose.Composable
|
import androidx.compose.Composable
|
||||||
import androidx.ui.core.*
|
import androidx.ui.core.ContextAmbient
|
||||||
import androidx.ui.foundation.Box
|
import androidx.ui.core.Text
|
||||||
import androidx.ui.graphics.*
|
|
||||||
import androidx.ui.graphics.colorspace.ColorSpace
|
|
||||||
import androidx.ui.graphics.colorspace.ColorSpaces
|
|
||||||
import androidx.ui.graphics.painter.ImagePainter
|
|
||||||
import androidx.ui.layout.*
|
import androidx.ui.layout.*
|
||||||
import androidx.ui.material.MaterialTheme
|
import androidx.ui.material.MaterialTheme
|
||||||
import androidx.ui.material.OutlinedButton
|
import androidx.ui.material.OutlinedButton
|
||||||
import androidx.ui.material.ripple.Ripple
|
import androidx.ui.material.ripple.Ripple
|
||||||
import androidx.ui.tooling.preview.Preview
|
import androidx.ui.tooling.preview.Preview
|
||||||
import androidx.ui.unit.Density
|
|
||||||
import androidx.ui.unit.PxSize
|
|
||||||
import androidx.ui.unit.dp
|
import androidx.ui.unit.dp
|
||||||
import androidx.ui.unit.toRect
|
|
||||||
import com.geeksville.analytics.DataPair
|
import com.geeksville.analytics.DataPair
|
||||||
import com.geeksville.android.GeeksvilleApplication
|
import com.geeksville.android.GeeksvilleApplication
|
||||||
import com.geeksville.android.Logging
|
import com.geeksville.android.Logging
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
|
import com.geeksville.mesh.model.Channel
|
||||||
import com.geeksville.mesh.model.UIState
|
import com.geeksville.mesh.model.UIState
|
||||||
|
import com.geeksville.mesh.model.toHumanString
|
||||||
|
|
||||||
/// The Compose IDE preview doesn't like the protobufs
|
|
||||||
data class Channel(val name: String, val num: Int)
|
|
||||||
|
|
||||||
object ChannelLog : Logging
|
object ChannelLog : Logging
|
||||||
|
|
||||||
/// Borrowed from Compose
|
|
||||||
class AndroidImage(val bitmap: Bitmap) : Image {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Image.width
|
|
||||||
*/
|
|
||||||
override val width: Int
|
|
||||||
get() = bitmap.width
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Image.height
|
|
||||||
*/
|
|
||||||
override val height: Int
|
|
||||||
get() = bitmap.height
|
|
||||||
|
|
||||||
override val config: ImageConfig get() = ImageConfig.Argb8888
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Image.colorSpace
|
|
||||||
*/
|
|
||||||
override val colorSpace: ColorSpace
|
|
||||||
get() = ColorSpaces.Srgb
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Image.hasAlpha
|
|
||||||
*/
|
|
||||||
override val hasAlpha: Boolean
|
|
||||||
get() = bitmap.hasAlpha()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see Image.nativeImage
|
|
||||||
*/
|
|
||||||
override val nativeImage: NativeImage
|
|
||||||
get() = bitmap
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @see
|
|
||||||
*/
|
|
||||||
override fun prepareToDraw() {
|
|
||||||
bitmap.prepareToDraw()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
|
||||||
// TODO(mount, malkov) : remove when RepaintBoundary is a modifier: b/149982905
|
|
||||||
// This is class and not val because if b/149985596
|
|
||||||
private object ClipModifier : DrawModifier {
|
|
||||||
override fun draw(density: Density, drawContent: () -> Unit, canvas: Canvas, size: PxSize) {
|
|
||||||
canvas.save()
|
|
||||||
canvas.clipRect(size.toRect())
|
|
||||||
drawContent()
|
|
||||||
canvas.restore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stolen from the Compose SimpleImage, replace with their real Image component someday
|
|
||||||
@Composable
|
|
||||||
fun ScaledImage(
|
|
||||||
image: Image,
|
|
||||||
modifier: Modifier = Modifier.None,
|
|
||||||
tint: Color? = null
|
|
||||||
) {
|
|
||||||
with(DensityAmbient.current) {
|
|
||||||
val imageModifier = ImagePainter(image).toModifier(
|
|
||||||
scaleFit = ScaleFit.FillMaxDimension,
|
|
||||||
colorFilter = tint?.let { ColorFilter(it, BlendMode.srcIn) }
|
|
||||||
)
|
|
||||||
Box(modifier + ClipModifier + imageModifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChannelContent(channel: Channel = Channel("Default", 7)) {
|
fun ChannelContent(channel: Channel?) {
|
||||||
analyticsScreen(name = "channel")
|
analyticsScreen(name = "channel")
|
||||||
|
|
||||||
val typography = MaterialTheme.typography()
|
val typography = MaterialTheme.typography()
|
||||||
val context = ContextAmbient.current
|
val context = ContextAmbient.current
|
||||||
|
|
||||||
Column(modifier = LayoutSize.Fill + LayoutPadding(16.dp)) {
|
Column(modifier = LayoutSize.Fill + LayoutPadding(16.dp)) {
|
||||||
Text(
|
if (channel != null) {
|
||||||
text = "Channel: ${channel.name}",
|
Text(
|
||||||
modifier = LayoutGravity.Center,
|
text = "Channel: ${channel.name}",
|
||||||
style = typography.h4
|
modifier = LayoutGravity.Center,
|
||||||
)
|
style = typography.h4
|
||||||
|
|
||||||
Row(modifier = LayoutGravity.Center) {
|
|
||||||
// simulated qr code
|
|
||||||
// val image = imageResource(id = R.drawable.qrcode)
|
|
||||||
val image = AndroidImage(UIState.getChannelQR(context))
|
|
||||||
|
|
||||||
ScaledImage(
|
|
||||||
image = image,
|
|
||||||
modifier = LayoutGravity.Center + LayoutSize.Min(200.dp, 200.dp)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Ripple(bounded = false) {
|
Row(modifier = LayoutGravity.Center) {
|
||||||
OutlinedButton(modifier = LayoutGravity.Center + LayoutPadding(start = 24.dp),
|
// simulated qr code
|
||||||
onClick = {
|
// val image = imageResource(id = R.drawable.qrcode)
|
||||||
GeeksvilleApplication.analytics.track(
|
val image = AndroidImage(UIState.getChannelQR(context))
|
||||||
"share",
|
|
||||||
DataPair("content_type", "channel")
|
|
||||||
) // track how many times users share channels
|
|
||||||
|
|
||||||
val sendIntent: Intent = Intent().apply {
|
ScaledImage(
|
||||||
action = Intent.ACTION_SEND
|
image = image,
|
||||||
putExtra(Intent.EXTRA_TEXT, UIState.getChannelUrl(context))
|
modifier = LayoutGravity.Center + LayoutSize.Min(200.dp, 200.dp)
|
||||||
putExtra(Intent.EXTRA_TITLE, "A URL for joining a Meshtastic mesh")
|
)
|
||||||
type = "text/plain"
|
|
||||||
}
|
|
||||||
|
|
||||||
val shareIntent = Intent.createChooser(sendIntent, null)
|
Ripple(bounded = false) {
|
||||||
context.startActivity(shareIntent)
|
OutlinedButton(modifier = LayoutGravity.Center + LayoutPadding(start = 24.dp),
|
||||||
}) {
|
onClick = {
|
||||||
VectorImage(
|
GeeksvilleApplication.analytics.track(
|
||||||
id = R.drawable.ic_twotone_share_24,
|
"share",
|
||||||
tint = palette.onBackground
|
DataPair("content_type", "channel")
|
||||||
)
|
) // track how many times users share channels
|
||||||
|
|
||||||
|
val sendIntent: Intent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
putExtra(Intent.EXTRA_TEXT, UIState.getChannelUrl(context))
|
||||||
|
putExtra(Intent.EXTRA_TITLE, "A URL for joining a Meshtastic mesh")
|
||||||
|
type = "text/plain"
|
||||||
|
}
|
||||||
|
|
||||||
|
val shareIntent = Intent.createChooser(sendIntent, null)
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
}) {
|
||||||
|
VectorImage(
|
||||||
|
id = R.drawable.ic_twotone_share_24,
|
||||||
|
tint = palette.onBackground
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Number: ${channel.num}",
|
text = "Number: ${channel.num}",
|
||||||
modifier = LayoutGravity.Center
|
modifier = LayoutGravity.Center
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = "Mode: Long range (but slow)",
|
text = "Mode: ${channel.modemConfig.toHumanString()}",
|
||||||
modifier = LayoutGravity.Center
|
modifier = LayoutGravity.Center
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,6 +91,6 @@ fun ChannelContent(channel: Channel = Channel("Default", 7)) {
|
||||||
fun previewChannel() {
|
fun previewChannel() {
|
||||||
// another bug? It seems modaldrawerlayout not yet supported in preview
|
// another bug? It seems modaldrawerlayout not yet supported in preview
|
||||||
MaterialTheme(colors = palette) {
|
MaterialTheme(colors = palette) {
|
||||||
ChannelContent()
|
ChannelContent(Channel.emulated)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,7 @@ private fun AppContent(openDrawer: () -> Unit) {
|
||||||
Screen.messages -> MessagesContent()
|
Screen.messages -> MessagesContent()
|
||||||
Screen.settings -> SettingsContent()
|
Screen.settings -> SettingsContent()
|
||||||
Screen.users -> HomeContent()
|
Screen.users -> HomeContent()
|
||||||
Screen.channel -> ChannelContent()
|
Screen.channel -> ChannelContent(UIState.getChannel())
|
||||||
Screen.map -> MapContent()
|
Screen.map -> MapContent()
|
||||||
else -> TODO()
|
else -> TODO()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f309ee8f9e9db37daabd7c76da683e052ef62f7a
|
Subproject commit 66e926740acb30518d1fdcb901d1cc0b0d48122c
|
Ładowanie…
Reference in New Issue