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 14c64d2e0..33bd1f7bb 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -10,11 +10,37 @@ import androidx.core.content.edit import com.geeksville.android.Logging import com.geeksville.mesh.IMeshService import com.geeksville.mesh.MeshProtos +import com.geeksville.mesh.MeshProtos.ChannelSettings.ModemConfig import com.geeksville.mesh.ui.getInitials import com.google.zxing.BarcodeFormat import com.google.zxing.MultiFormatWriter 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 object UIState : Logging { @@ -34,6 +60,8 @@ object UIState : Logging { /// our activity will read this from prefs or set it to the empty string var ownerName: String = "MrInIDE Ownername" + fun getChannel() = radioConfig.value?.channelSettings?.let { Channel(it) } + /// 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 @@ -52,8 +80,7 @@ object UIState : Logging { } } - fun getChannelQR(context: Context): Bitmap - { + fun getChannelQR(context: Context): Bitmap { val multiFormatWriter = MultiFormatWriter() val bitMatrix = diff --git a/app/src/main/java/com/geeksville/mesh/ui/AndroidImage.kt b/app/src/main/java/com/geeksville/mesh/ui/AndroidImage.kt new file mode 100644 index 000000000..b53565b89 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/AndroidImage.kt @@ -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() + } +} 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 3f289c7a9..2ba40fa66 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Channel.kt @@ -1,164 +1,87 @@ package com.geeksville.mesh.ui import android.content.Intent -import android.graphics.Bitmap import androidx.compose.Composable -import androidx.ui.core.* -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.core.ContextAmbient +import androidx.ui.core.Text import androidx.ui.layout.* import androidx.ui.material.MaterialTheme import androidx.ui.material.OutlinedButton import androidx.ui.material.ripple.Ripple 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.toRect import com.geeksville.analytics.DataPair 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 -/// The Compose IDE preview doesn't like the protobufs -data class Channel(val name: String, val num: Int) 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 -fun ChannelContent(channel: Channel = Channel("Default", 7)) { +fun ChannelContent(channel: Channel?) { analyticsScreen(name = "channel") val typography = MaterialTheme.typography() val context = ContextAmbient.current Column(modifier = LayoutSize.Fill + LayoutPadding(16.dp)) { - Text( - text = "Channel: ${channel.name}", - 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) + if (channel != null) { + Text( + text = "Channel: ${channel.name}", + modifier = LayoutGravity.Center, + style = typography.h4 ) - Ripple(bounded = false) { - OutlinedButton(modifier = LayoutGravity.Center + LayoutPadding(start = 24.dp), - onClick = { - GeeksvilleApplication.analytics.track( - "share", - DataPair("content_type", "channel") - ) // track how many times users share channels + Row(modifier = LayoutGravity.Center) { + // simulated qr code + // val image = imageResource(id = R.drawable.qrcode) + val image = AndroidImage(UIState.getChannelQR(context)) - 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" - } + ScaledImage( + image = image, + modifier = LayoutGravity.Center + LayoutSize.Min(200.dp, 200.dp) + ) - val shareIntent = Intent.createChooser(sendIntent, null) - context.startActivity(shareIntent) - }) { - VectorImage( - id = R.drawable.ic_twotone_share_24, - tint = palette.onBackground - ) + Ripple(bounded = false) { + OutlinedButton(modifier = LayoutGravity.Center + LayoutPadding(start = 24.dp), + onClick = { + GeeksvilleApplication.analytics.track( + "share", + 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 = "Number: ${channel.num}", - modifier = LayoutGravity.Center - ) - Text( - text = "Mode: Long range (but slow)", - modifier = LayoutGravity.Center - ) + Text( + text = "Number: ${channel.num}", + modifier = LayoutGravity.Center + ) + Text( + text = "Mode: ${channel.modemConfig.toHumanString()}", + modifier = LayoutGravity.Center + ) + } } } @@ -168,6 +91,6 @@ fun ChannelContent(channel: Channel = Channel("Default", 7)) { fun previewChannel() { // another bug? It seems modaldrawerlayout not yet supported in preview MaterialTheme(colors = palette) { - ChannelContent() + ChannelContent(Channel.emulated) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt index b1a172e11..600c9ca21 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt @@ -145,7 +145,7 @@ private fun AppContent(openDrawer: () -> Unit) { Screen.messages -> MessagesContent() Screen.settings -> SettingsContent() Screen.users -> HomeContent() - Screen.channel -> ChannelContent() + Screen.channel -> ChannelContent(UIState.getChannel()) Screen.map -> MapContent() else -> TODO() } diff --git a/app/src/main/proto b/app/src/main/proto index f309ee8f9..66e926740 160000 --- a/app/src/main/proto +++ b/app/src/main/proto @@ -1 +1 @@ -Subproject commit f309ee8f9e9db37daabd7c76da683e052ef62f7a +Subproject commit 66e926740acb30518d1fdcb901d1cc0b0d48122c