diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index 406967d37..0f7a67acc 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -16,8 +16,13 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.ui.core.setContent import com.geeksville.android.Logging +import com.geeksville.mesh.model.MessagesState +import com.geeksville.mesh.model.NodeDB +import com.geeksville.mesh.model.TextMessage +import com.geeksville.mesh.model.UIState import com.geeksville.mesh.service.* -import com.geeksville.mesh.ui.* +import com.geeksville.mesh.ui.MeshApp +import com.geeksville.mesh.ui.getInitials import com.geeksville.util.exceptionReporter import com.google.android.gms.auth.api.signin.GoogleSignIn import com.google.android.gms.auth.api.signin.GoogleSignInAccount @@ -315,7 +320,12 @@ class MainActivity : AppCompatActivity(), Logging, // FIXME - use the real time from the packet // FIXME - don't just slam in a new list each time, it probably causes extra drawing. Figure out how to be Compose smarter... val modded = MessagesState.messages.value.toMutableList() - modded.add(TextMessage(sender, payload.toString(utf8))) + modded.add( + TextMessage( + sender, + payload.toString(utf8) + ) + ) MessagesState.messages.value = modded } else -> TODO() diff --git a/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt new file mode 100644 index 000000000..df7725945 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/MessagesState.kt @@ -0,0 +1,36 @@ +package com.geeksville.mesh.model + +import androidx.compose.mutableStateOf +import com.geeksville.android.Logging +import java.util.* + +/** + * the model object for a text message + */ +data class TextMessage(val from: String, val text: String, val date: Date = Date()) + + +object MessagesState : Logging { + val testTexts = listOf( + TextMessage( + "+16508675310", + "I found the cache" + ), + TextMessage( + "+16508675311", + "Help! I've fallen and I can't get up." + ) + ) + + // If the following (unused otherwise) line is commented out, the IDE preview window works. + // if left in the preview always renders as empty. + val messages = mutableStateOf(testTexts, { a, b -> + a.size == b.size // If the # of messages changes, consider it important for rerender + }) + + fun addMessage(m: TextMessage) { + val l = messages.value.toMutableList() + l.add(m) + messages.value = l + } +} diff --git a/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt new file mode 100644 index 000000000..c1d12d9aa --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/NodeDB.kt @@ -0,0 +1,48 @@ +package com.geeksville.mesh.model + +import androidx.compose.mutableStateOf +import com.geeksville.mesh.MeshUser +import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.Position + +object NodeDB { + private val testPositions = arrayOf( + Position(32.776665, -96.796989, 35), // dallas + Position(32.960758, -96.733521, 35), // richardson + Position( + 32.912901, + -96.781776, + 35 + ) // north dallas + ) + + val testNodeNoPosition = NodeInfo( + 8, + MeshUser( + "+16508765308".format(8), + "Kevin MesterNoLoc", + "KLO" + ), + null, + 12345 + ) + + val testNodes = testPositions.mapIndexed { index, it -> + NodeInfo( + 9 + index, + MeshUser( + "+165087653%02d".format(9 + index), + "Kevin Mester$index", + "KM$index" + ), + it, + 12345 + ) + } + + /// The unique ID of our node + val myId = mutableStateOf("+16508765309") + + /// A map from nodeid to to nodeinfo + val nodes = mutableStateOf(testNodes.map { it.user!!.id to it }.toMap()) +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt new file mode 100644 index 000000000..0a8812532 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -0,0 +1,32 @@ +package com.geeksville.mesh.model + +import android.util.Base64 +import androidx.compose.mutableStateOf +import com.geeksville.mesh.MeshProtos + +/// FIXME - figure out how to merge this staate with the AppStatus Model +object UIState { + + /// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME + // lateinit var googleSignInClient: GoogleSignInClient + + + /// Are we connected to our radio device + val isConnected = mutableStateOf(false) + + /// various radio settings (including the channel) + val radioConfig = mutableStateOf(MeshProtos.RadioConfig.getDefaultInstance()) + + /// our name in hte radio + /// Note, we generate owner initials automatically for now + val ownerName = mutableStateOf("fixme readfromprefs") + + /// Return an URL that represents the current channel values + val channelUrl + get(): String { + val channelBytes = radioConfig.value.channelSettings.toByteArray() + val enc = Base64.encodeToString(channelBytes, Base64.URL_SAFE + Base64.NO_WRAP) + + return "https://www.meshtastic.org/c/$enc" + } +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt b/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt index b674ccc79..fbb29944e 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/LocationUtils.kt @@ -12,177 +12,175 @@ import kotlin.math.sin ******************************************************************************/ -object LocationUtils { - /** - * Format as degrees, minutes, secs - * - * @param degIn - * @param isLatitude - * @return a string like 120deg - */ - fun degreesToDMS( - _degIn: Double, - isLatitude: Boolean - ): Array { - var degIn = _degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' - degIn = Math.abs(degIn) - val degOut = degIn.toInt() - val minutes = 60 * (degIn - degOut) - val minwhole = minutes.toInt() - val seconds = (minutes - minwhole) * 60 - return arrayOf( - Integer.toString(degOut), Integer.toString(minwhole), - java.lang.Double.toString(seconds), - Character.toString(dirLetter) +/** + * Format as degrees, minutes, secs + * + * @param degIn + * @param isLatitude + * @return a string like 120deg + */ +fun degreesToDMS( + _degIn: Double, + isLatitude: Boolean +): Array { + var degIn = _degIn + val isPos = degIn >= 0 + val dirLetter = + if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' + degIn = Math.abs(degIn) + val degOut = degIn.toInt() + val minutes = 60 * (degIn - degOut) + val minwhole = minutes.toInt() + val seconds = (minutes - minwhole) * 60 + return arrayOf( + Integer.toString(degOut), Integer.toString(minwhole), + java.lang.Double.toString(seconds), + Character.toString(dirLetter) + ) +} + +fun degreesToDM(_degIn: Double, isLatitude: Boolean): Array { + var degIn = _degIn + val isPos = degIn >= 0 + val dirLetter = + if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' + degIn = Math.abs(degIn) + val degOut = degIn.toInt() + val minutes = 60 * (degIn - degOut) + val seconds = 0 + return arrayOf( + Integer.toString(degOut), java.lang.Double.toString(minutes), + Integer.toString(seconds), + Character.toString(dirLetter) + ) +} + +fun degreesToD(_degIn: Double, isLatitude: Boolean): Array { + var degIn = _degIn + val isPos = degIn >= 0 + val dirLetter = + if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' + degIn = Math.abs(degIn) + val degOut = degIn + val minutes = 0 + val seconds = 0 + return arrayOf( + java.lang.Double.toString(degOut), Integer.toString(minutes), + Integer.toString(seconds), + Character.toString(dirLetter) + ) +} + +/** + * A not super efficent mapping from a starting lat/long + a distance at a + * certain direction + * + * @param lat + * @param longitude + * @param distMeters + * @param theta + * in radians, 0 == north + * @return an array with lat and long + */ +fun addDistance( + lat: Double, + longitude: Double, + distMeters: Double, + theta: Double +): DoubleArray { + val dx = distMeters * Math.sin(theta) // theta measured clockwise + // from due north + val dy = distMeters * Math.cos(theta) // dx, dy same units as R + val dLong = dx / (111320 * Math.cos(lat)) // dx, dy in meters + val dLat = dy / 110540 // result in degrees long/lat + return doubleArrayOf(lat + dLat, longitude + dLong) +} + +fun LatLongToMeter( + lat_a: Double, + lng_a: Double, + lat_b: Double, + lng_b: Double +): Double { + val pk = (180 / 3.14169) + val a1 = lat_a / pk + val a2 = lng_a / pk + val b1 = lat_b / pk + val b2 = lng_b / pk + val t1 = + Math.cos(a1) * Math.cos(a2) * Math.cos(b1) * Math.cos( + b2 ) - } - - fun degreesToDM(_degIn: Double, isLatitude: Boolean): Array { - var degIn = _degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' - degIn = Math.abs(degIn) - val degOut = degIn.toInt() - val minutes = 60 * (degIn - degOut) - val seconds = 0 - return arrayOf( - Integer.toString(degOut), java.lang.Double.toString(minutes), - Integer.toString(seconds), - Character.toString(dirLetter) + val t2 = + Math.cos(a1) * Math.sin(a2) * Math.cos(b1) * Math.sin( + b2 ) - } + val t3 = Math.sin(a1) * Math.sin(b1) + var tt = Math.acos(t1 + t2 + t3) + if (java.lang.Double.isNaN(tt)) tt = 0.0 // Must have been the same point? + return 6366000 * tt +} - fun degreesToD(_degIn: Double, isLatitude: Boolean): Array { - var degIn = _degIn - val isPos = degIn >= 0 - val dirLetter = - if (isLatitude) if (isPos) 'N' else 'S' else if (isPos) 'E' else 'W' - degIn = Math.abs(degIn) - val degOut = degIn - val minutes = 0 - val seconds = 0 - return arrayOf( - java.lang.Double.toString(degOut), Integer.toString(minutes), - Integer.toString(seconds), - Character.toString(dirLetter) - ) - } +/** + * Convert degrees/mins/secs to a single double + * + * @param degrees + * @param minutes + * @param seconds + * @param isPostive + * @return + */ +fun DMSToDegrees( + degrees: Int, + minutes: Int, + seconds: Float, + isPostive: Boolean +): Double { + return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) +} - /** - * A not super efficent mapping from a starting lat/long + a distance at a - * certain direction - * - * @param lat - * @param longitude - * @param distMeters - * @param theta - * in radians, 0 == north - * @return an array with lat and long - */ - fun addDistance( - lat: Double, - longitude: Double, - distMeters: Double, - theta: Double - ): DoubleArray { - val dx = distMeters * Math.sin(theta) // theta measured clockwise - // from due north - val dy = distMeters * Math.cos(theta) // dx, dy same units as R - val dLong = dx / (111320 * Math.cos(lat)) // dx, dy in meters - val dLat = dy / 110540 // result in degrees long/lat - return doubleArrayOf(lat + dLat, longitude + dLong) - } +fun DMSToDegrees( + degrees: Double, + minutes: Double, + seconds: Double, + isPostive: Boolean +): Double { + return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) +} - fun LatLongToMeter( - lat_a: Double, - lng_a: Double, - lat_b: Double, - lng_b: Double - ): Double { - val pk = (180 / 3.14169) - val a1 = lat_a / pk - val a2 = lng_a / pk - val b1 = lat_b / pk - val b2 = lng_b / pk - val t1 = - Math.cos(a1) * Math.cos(a2) * Math.cos(b1) * Math.cos( - b2 - ) - val t2 = - Math.cos(a1) * Math.sin(a2) * Math.cos(b1) * Math.sin( - b2 - ) - val t3 = Math.sin(a1) * Math.sin(b1) - var tt = Math.acos(t1 + t2 + t3) - if (java.lang.Double.isNaN(tt)) tt = 0.0 // Must have been the same point? - return 6366000 * tt - } +/** + * Computes the bearing in degrees between two points on Earth. + * + * @param lat1 + * Latitude of the first point + * @param lon1 + * Longitude of the first point + * @param lat2 + * Latitude of the second point + * @param lon2 + * Longitude of the second point + * @return Bearing between the two points in degrees. A value of 0 means due + * north. + */ +fun bearing( + lat1: Double, + lon1: Double, + lat2: Double, + lon2: Double +): Double { + val lat1Rad = Math.toRadians(lat1) + val lat2Rad = Math.toRadians(lat2) + val deltaLonRad = Math.toRadians(lon2 - lon1) + val y = sin(deltaLonRad) * cos(lat2Rad) + val x = + cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) + * Math.cos(deltaLonRad)) + return radToBearing(Math.atan2(y, x)) +} - /** - * Convert degrees/mins/secs to a single double - * - * @param degrees - * @param minutes - * @param seconds - * @param isPostive - * @return - */ - fun DMSToDegrees( - degrees: Int, - minutes: Int, - seconds: Float, - isPostive: Boolean - ): Double { - return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) - } - - fun DMSToDegrees( - degrees: Double, - minutes: Double, - seconds: Double, - isPostive: Boolean - ): Double { - return (if (isPostive) 1 else -1) * (degrees + minutes / 60.0 + seconds / 3600.0) - } - - /** - * Computes the bearing in degrees between two points on Earth. - * - * @param lat1 - * Latitude of the first point - * @param lon1 - * Longitude of the first point - * @param lat2 - * Latitude of the second point - * @param lon2 - * Longitude of the second point - * @return Bearing between the two points in degrees. A value of 0 means due - * north. - */ - fun bearing( - lat1: Double, - lon1: Double, - lat2: Double, - lon2: Double - ): Double { - val lat1Rad = Math.toRadians(lat1) - val lat2Rad = Math.toRadians(lat2) - val deltaLonRad = Math.toRadians(lon2 - lon1) - val y = sin(deltaLonRad) * cos(lat2Rad) - val x = - cos(lat1Rad) * sin(lat2Rad) - (sin(lat1Rad) * cos(lat2Rad) - * Math.cos(deltaLonRad)) - return radToBearing(Math.atan2(y, x)) - } - - /** - * Converts an angle in radians to degrees - */ - fun radToBearing(rad: Double): Double { - return (Math.toDegrees(rad) + 360) % 360 - } +/** + * Converts an angle in radians to degrees + */ +fun radToBearing(rad: Double): Double { + return (Math.toDegrees(rad) + 360) % 360 } \ No newline at end of file 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 270e78c6c..f9601c6e6 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt @@ -18,6 +18,8 @@ import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.geeksville.android.Logging import com.geeksville.mesh.R +import com.geeksville.mesh.model.NodeDB +import com.geeksville.mesh.model.UIState object UILog : Logging diff --git a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt index cbbb945de..00a82c50b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Messages.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Messages.kt @@ -1,7 +1,6 @@ package com.geeksville.mesh.ui import androidx.compose.Composable -import androidx.compose.mutableStateOf import androidx.compose.state import androidx.ui.core.Modifier import androidx.ui.core.Text @@ -20,37 +19,13 @@ import androidx.ui.material.surface.Surface import androidx.ui.text.TextStyle import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp -import com.geeksville.android.Logging -import com.geeksville.mesh.ui.MessagesState.messages +import com.geeksville.mesh.model.MessagesState +import com.geeksville.mesh.model.MessagesState.messages +import com.geeksville.mesh.model.NodeDB +import com.geeksville.mesh.model.TextMessage import java.text.SimpleDateFormat -import java.util.* -/** - * the model object for a text message - */ -data class TextMessage(val from: String, val text: String, val date: Date = Date()) - - -object MessagesState : Logging { - val testTexts = listOf( - TextMessage("+6508675310", "I found the cache"), - TextMessage("+6508675311", "Help! I've fallen and I can't get up.") - ) - - // If the following (unused otherwise) line is commented out, the IDE preview window works. - // if left in the preview always renders as empty. - val messages = mutableStateOf(MessagesState.testTexts, { a, b -> - a.size == b.size // If the # of messages changes, consider it important for rerender - }) - - fun addMessage(m: TextMessage) { - val l = messages.value.toMutableList() - l.add(m) - messages.value = l - } -} - private val dateFormat = SimpleDateFormat("h:mm a") val TimestampEmphasis = object : Emphasis { @@ -129,7 +104,12 @@ fun MessagesContent() { imeAction = ImeAction.Send, onImeActionPerformed = { MessagesState.info("did IME action") - MessagesState.addMessage(TextMessage("fixme", message.value)) + MessagesState.addMessage( + TextMessage( + "fixme", + message.value + ) + ) }, modifier = LayoutPadding(4.dp) ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeInfoCard.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeInfoCard.kt index efd63658e..6edba21d2 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeInfoCard.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeInfoCard.kt @@ -10,6 +10,7 @@ import androidx.ui.tooling.preview.Preview import androidx.ui.unit.dp import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R +import com.geeksville.mesh.model.NodeDB import androidx.ui.core.Modifier as Modifier1 diff --git a/app/src/main/java/com/geeksville/mesh/ui/Status.kt b/app/src/main/java/com/geeksville/mesh/ui/Status.kt index d7fc68fed..3d03495e5 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/Status.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/Status.kt @@ -1,9 +1,7 @@ package com.geeksville.mesh.ui -import android.util.Base64 import androidx.compose.Model -import androidx.compose.mutableStateOf -import com.geeksville.mesh.* +import com.geeksville.mesh.R data class ScreenInfo(val icon: Int, val label: String) @@ -22,72 +20,6 @@ object AppStatus { var currentScreen: ScreenInfo = Screen.messages } -object NodeDB { - private val testPositions = arrayOf( - Position(32.776665, -96.796989, 35), // dallas - Position(32.960758, -96.733521, 35), // richardson - Position( - 32.912901, - -96.781776, - 35 - ) // north dallas - ) - - val testNodeNoPosition = NodeInfo( - 8, - MeshUser( - "+6508765308".format(8), - "Kevin MesterNoLoc", - "KLO" - ), - null, - 12345 - ) - - val testNodes = testPositions.mapIndexed { index, it -> - NodeInfo( - 9 + index, - MeshUser( - "+65087653%02d".format(9 + index), - "Kevin Mester$index", - "KM$index" - ), - it, - 12345 - ) - } - - - /// A map from nodeid to to nodeinfo - val nodes = mutableStateOf(testNodes.map { it.user!!.id to it }.toMap()) -} - -/// FIXME - figure out how to merge this staate with the AppStatus Model -object UIState { - - /// Kinda ugly - created in the activity but used from Compose - figure out if there is a cleaner way GIXME - // lateinit var googleSignInClient: GoogleSignInClient - - - /// Are we connected to our radio device - val isConnected = mutableStateOf(false) - - /// various radio settings (including the channel) - val radioConfig = mutableStateOf(MeshProtos.RadioConfig.getDefaultInstance()) - - /// our name in hte radio - /// Note, we generate owner initials automatically for now - val ownerName = mutableStateOf("fixme readfromprefs") - - /// Return an URL that represents the current channel values - val channelUrl - get(): String { - val channelBytes = radioConfig.value.channelSettings.toByteArray() - val enc = Base64.encodeToString(channelBytes, Base64.URL_SAFE + Base64.NO_WRAP) - - return "https://www.meshtastic.org/c/$enc" - } -} /** * Temporary solution pending navigation support.