First Commit of WearSignalK2
This commit is contained in:
81
app/build.gradle.kts
Normal file
81
app/build.gradle.kts
Normal file
@@ -0,0 +1,81 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "net.dalsgaard.wearsignalk"
|
||||
compileSdk = 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "net.dalsgaard.wearsignalk"
|
||||
minSdk = 30
|
||||
targetSdk = 33
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = false
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.3"
|
||||
}
|
||||
packaging {
|
||||
resources {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("com.google.android.gms:play-services-wearable:18.1.0")
|
||||
implementation("androidx.percentlayout:percentlayout:1.0.0")
|
||||
implementation("androidx.legacy:legacy-support-v4:1.0.0")
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.1")
|
||||
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||
implementation("androidx.compose.ui:ui")
|
||||
implementation("androidx.compose.ui:ui-tooling-preview")
|
||||
implementation("androidx.wear.compose:compose-material:1.0.0")
|
||||
implementation("androidx.wear.compose:compose-foundation:1.0.0")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
|
||||
implementation("androidx.activity:activity-compose:1.5.1")
|
||||
androidTestImplementation(platform("androidx.compose:compose-bom:2023.03.00"))
|
||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
|
||||
debugImplementation("androidx.compose.ui:ui-tooling")
|
||||
debugImplementation("androidx.compose.ui:ui-test-manifest")
|
||||
|
||||
// websocket functionality
|
||||
implementation("io.ktor:ktor-client-core:2.3.4")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.4")
|
||||
implementation("io.ktor:ktor-client-websockets:2.3.4")
|
||||
|
||||
// jsonpath functionality
|
||||
implementation("com.nfeld.jsonpathkt:jsonpathkt:2.0.1")
|
||||
|
||||
// kotlin flow functionality for state propagation
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
|
||||
|
||||
}
|
||||
21
app/proguard-rules.pro
vendored
Normal file
21
app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
41
app/src/main/AndroidManifest.xml
Normal file
41
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<uses-library
|
||||
android:name="com.google.android.wearable"
|
||||
android:required="true" />
|
||||
|
||||
<!--
|
||||
Set to true if your app is Standalone, that is, it does not require the handheld
|
||||
app to run.
|
||||
-->
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
|
||||
<activity
|
||||
android:name=".presentation.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@android:style/Theme.DeviceDefault">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,215 @@
|
||||
package net.dalsgaard.wearsignalk.connection
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.nfeld.jsonpathkt.JsonPath
|
||||
import com.nfeld.jsonpathkt.extension.read
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.plugins.HttpTimeout
|
||||
import io.ktor.client.plugins.websocket.WebSockets
|
||||
import io.ktor.client.plugins.websocket.webSocket
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.websocket.send
|
||||
import io.ktor.websocket.Frame
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.Instant
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
private const val TAG = "SignalKConnection"
|
||||
|
||||
private const val SUBSCRIPTION = "{\n" +
|
||||
" \"context\": \"vessels.self\",\n" +
|
||||
" \"subscribe\": [\n" +
|
||||
" {\n" +
|
||||
" \"path\": \"navigation.datetime\",\n" +
|
||||
" \"period\": 5000\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"path\": \"navigation.courseRhumbline.nextPoint\",\n" +
|
||||
" \"period\": 5000\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"path\": \"navigation.courseOverGroundTrue\",\n" +
|
||||
" \"period\": 5000\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"path\": \"navigation.position\",\n" +
|
||||
" \"period\": 5000\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"path\": \"navigation.speedOverGround\",\n" +
|
||||
" \"period\": 5000\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}\n"
|
||||
|
||||
|
||||
|
||||
|
||||
public class KDataFlow<T>(inflow: Channel<T>, default: T): ViewModel() {
|
||||
val data = flow { while(true) { emit(inflow.receive()) } }
|
||||
.stateIn<T>(viewModelScope, SharingStarted.Lazily, default)
|
||||
}
|
||||
|
||||
|
||||
data class Position (val latitude: Double = 0.0, val longitude: Double = 0.0)
|
||||
|
||||
/**
|
||||
* Interface for SignalK data values to KData structure mapping
|
||||
*/
|
||||
interface SignalKMapper {
|
||||
suspend fun update(json : JsonNode) : Boolean
|
||||
}
|
||||
|
||||
// navigation.datetime
|
||||
class NavigationDatetimeMapper(val outflow: Channel<Instant>) : SignalKMapper {
|
||||
override suspend fun update(json: JsonNode): Boolean {
|
||||
val tstamputc = json?.read<String>("value")
|
||||
val t = Instant.parse(tstamputc)
|
||||
outflow.send(t)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// navigation.courseRhumbline.nextPoint
|
||||
class NavigationCourseRhumblineNextPointMapper : SignalKMapper {
|
||||
override suspend fun update(json: JsonNode): Boolean {
|
||||
val point = Pair(json?.read<Double>("value.latitude"), json?.read<Double>("value.longitude"))
|
||||
/*
|
||||
data.nextPointLatitude = point?.first
|
||||
data.nextPointLongitude = point?.second
|
||||
|
||||
data.nextPointBearingTrue = json?.read<Double>("bearingTrue.value")
|
||||
data.nextPointVMG = json?.read<Double>("velocityMadeGood.value")
|
||||
data.nextPointDistance = json?.read<Double>("distance.value")
|
||||
*/
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationPositionMapper(val outflow: Channel<Position>) : SignalKMapper {
|
||||
override suspend fun update(json: JsonNode): Boolean {
|
||||
val point = Pair(json?.read<Double>("value.latitude"), json?.read<Double>("value.longitude"))
|
||||
if (point.first != null && point.second != null) {
|
||||
outflow.send(Position(point.first!!, point.second!!))
|
||||
return true
|
||||
} else return false
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationSpeedOverGroundMapper(val outflow: Channel<Double>) : SignalKMapper {
|
||||
override suspend fun update(json: JsonNode): Boolean {
|
||||
val speedOverGround = json?.read<Double>("value")
|
||||
if (speedOverGround != null) outflow.send(speedOverGround)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
class NavigationCourseOverGroundMapper(val outflow: Channel<Double>) : SignalKMapper {
|
||||
override suspend fun update(json: JsonNode): Boolean {
|
||||
val courseOverGroundTrue = json?.read<Double>("value")
|
||||
if (courseOverGroundTrue != null) outflow.send(courseOverGroundTrue)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SignalKConnection {
|
||||
|
||||
private val ws_url = "ws://nora.dalsgaard.net:3000/signalk/v1/stream?subscribe=none"
|
||||
|
||||
private var bgThread: Thread? = null;
|
||||
private var keepRunning : Boolean = true
|
||||
|
||||
val navigationPositionChannel = Channel<Position>()
|
||||
val datetimeChannel = Channel<Instant>()
|
||||
val courseOverGroundTrueChannel = Channel<Double>()
|
||||
val speedOverGroundChannel = Channel<Double>()
|
||||
|
||||
private val signalKmapper : Map<String,SignalKMapper>
|
||||
init {
|
||||
signalKmapper = mapOf(
|
||||
"navigation.datetime" to NavigationDatetimeMapper(datetimeChannel),
|
||||
"navigation.courseOverGroundTrue" to NavigationCourseOverGroundMapper(courseOverGroundTrueChannel),
|
||||
"navigation.speedOverGround" to NavigationSpeedOverGroundMapper(speedOverGroundChannel),
|
||||
"navigation.position" to NavigationPositionMapper(navigationPositionChannel),
|
||||
"navigation.courseRhumbline.nextPoint" to NavigationCourseRhumblineNextPointMapper()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
public fun getFlowPosition () : KDataFlow<Position> {
|
||||
return KDataFlow(navigationPositionChannel, Position())
|
||||
}
|
||||
|
||||
public fun getFlowDatetime () : KDataFlow<Instant> {
|
||||
return KDataFlow(datetimeChannel, Instant.ofEpochMilli(0))
|
||||
}
|
||||
|
||||
public fun getFlowCourseOverGround () : KDataFlow<Double> {
|
||||
return KDataFlow(courseOverGroundTrueChannel, 0.0)
|
||||
}
|
||||
|
||||
public fun getFlowSpeedOverGround () : KDataFlow<Double> {
|
||||
return KDataFlow(speedOverGroundChannel, 0.0)
|
||||
}
|
||||
|
||||
|
||||
public fun stop () {
|
||||
keepRunning = false
|
||||
}
|
||||
|
||||
public fun start () {
|
||||
this.bgThread = thread(start = true, isDaemon = true) {
|
||||
while (keepRunning) {
|
||||
Log.d(TAG, "Inside ConnectionThread")
|
||||
val client = HttpClient {
|
||||
install(WebSockets)
|
||||
install(HttpTimeout) {
|
||||
connectTimeoutMillis = 10_000
|
||||
requestTimeoutMillis = 10_000
|
||||
}
|
||||
}
|
||||
runBlocking {
|
||||
client.webSocket(
|
||||
method = HttpMethod.Get,
|
||||
host = "nora.dalsgaard.net",
|
||||
port = 3000,
|
||||
path = "/signalk/v1/stream?subscribe=none"
|
||||
) {
|
||||
// get welcome message
|
||||
val msg = incoming.receive().data.decodeToString()
|
||||
Log.d(TAG, "Welcome message: $msg")
|
||||
send(SUBSCRIPTION)
|
||||
Log.d(TAG, "sent: $SUBSCRIPTION")
|
||||
while (keepRunning) {
|
||||
val othersMessage = incoming.receive() as? Frame.Text ?: continue
|
||||
val body = othersMessage.data.decodeToString()
|
||||
val json = JsonPath.parse(body)
|
||||
val updates = json?.read<List<JsonNode>>("$.updates[*].values[*]")
|
||||
if (updates != null) {
|
||||
Log.d(TAG, "Got updates")
|
||||
for (i in updates) {
|
||||
val path = i.read<String>("path")
|
||||
signalKmapper[path]?.update(i)
|
||||
}
|
||||
}
|
||||
// Log.d(TAG, "data is now: $data")
|
||||
}
|
||||
Log.d(TAG, "closing websocket")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
/* While this template provides a good starting point for using Wear Compose, you can always
|
||||
* take a look at https://github.com/android/wear-os-samples/tree/main/ComposeStarter and
|
||||
* https://github.com/android/wear-os-samples/tree/main/ComposeAdvanced to find the most up to date
|
||||
* changes to the libraries and their usages.
|
||||
*/
|
||||
|
||||
package net.dalsgaard.wearsignalk.presentation
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.util.Log
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.wear.compose.material.MaterialTheme
|
||||
import androidx.wear.compose.material.Text
|
||||
import net.dalsgaard.wearsignalk.R
|
||||
import net.dalsgaard.wearsignalk.connection.KDataFlow
|
||||
import net.dalsgaard.wearsignalk.connection.Position
|
||||
import net.dalsgaard.wearsignalk.connection.SignalKConnection
|
||||
import net.dalsgaard.wearsignalk.presentation.theme.WearSignalKTheme
|
||||
import net.dalsgaard.wearsignalk.util.Design
|
||||
import net.dalsgaard.wearsignalk.util.Vector
|
||||
import net.dalsgaard.wearsignalk.util.awd_ident
|
||||
import net.dalsgaard.wearsignalk.util.cog_ident
|
||||
import net.dalsgaard.wearsignalk.util.compass_rose
|
||||
import net.dalsgaard.wearsignalk.util.eyeMatrix
|
||||
import net.dalsgaard.wearsignalk.util.forSquareScreen
|
||||
import net.dalsgaard.wearsignalk.util.latToString
|
||||
import net.dalsgaard.wearsignalk.util.lonToString
|
||||
import net.dalsgaard.wearsignalk.util.rotMatrix
|
||||
import net.dalsgaard.wearsignalk.util.scaleMatrix
|
||||
import net.dalsgaard.wearsignalk.util.ship
|
||||
import java.time.Instant
|
||||
import java.time.LocalTime
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.min
|
||||
import kotlin.math.round
|
||||
import kotlin.math.sin
|
||||
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
var conn = SignalKConnection()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.d(TAG, "onCreate in MainActivity")
|
||||
conn.start()
|
||||
|
||||
setContent {
|
||||
// WearApp("Android", conn.getKData())
|
||||
WearApp("Android", conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun drawDesign(design: Design, dr: DrawScope) {
|
||||
for (t in design.traces) {
|
||||
var idx_iterator = t.indices.iterator()
|
||||
var last_idx = idx_iterator.next()
|
||||
while (idx_iterator.hasNext()) {
|
||||
val idx = idx_iterator.next()
|
||||
dr.drawLine(t.color, start=Offset(design.points[last_idx].x, design.points[last_idx].y),
|
||||
end=Offset(design.points[idx].x, design.points[idx].y), strokeWidth = t.width)
|
||||
last_idx = idx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearApp(greetingName: String, conn: SignalKConnection) {
|
||||
|
||||
val cog_true_flow = conn.getFlowCourseOverGround()
|
||||
WearSignalKTheme {
|
||||
/* If you have enough items in your list, use [ScalingLazyColumn] which is an optimized
|
||||
* version of LazyColumn for wear devices with some added features. For more information,
|
||||
* see d.android.com/wear/compose.
|
||||
*/
|
||||
ship(conn.getFlowCourseOverGround())
|
||||
|
||||
|
||||
Column (modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Color.Transparent)
|
||||
.drawBehind
|
||||
{
|
||||
val min_dim = min(size.width, size.height)
|
||||
val ship_ship = forSquareScreen(ship, min_dim)
|
||||
drawDesign(ship_ship, this)
|
||||
|
||||
}, verticalArrangement = Arrangement.Center) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) { Time(conn.getFlowDatetime())
|
||||
}
|
||||
Row (modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Column (modifier = Modifier, verticalArrangement = Arrangement.Center,Alignment.CenterHorizontally) {
|
||||
courseOverGroundTrue(conn.getFlowCourseOverGround())
|
||||
}
|
||||
Column (modifier = Modifier, verticalArrangement = Arrangement.Center) {
|
||||
speedOverGround(conn.getFlowSpeedOverGround())
|
||||
}
|
||||
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Position(conn.getFlowPosition())
|
||||
//Text("JD")
|
||||
//Text(latToString(data.latitude!!))
|
||||
//Text(lonToString(data.longitude!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WearAppPreview(greetingName: String) {
|
||||
WearSignalKTheme {
|
||||
/* If you have enough items in your list, use [ScalingLazyColumn] which is an optimized
|
||||
* version of LazyColumn for wear devices with some added features. For more information,
|
||||
* see d.android.com/wear/compose.
|
||||
*/
|
||||
Column(
|
||||
modifier = Modifier,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
val data = Position(12.2, 55.2)
|
||||
Column (modifier = Modifier, verticalArrangement = Arrangement.Center,Alignment.CenterHorizontally) {
|
||||
|
||||
Text("Boat Position Pre", Modifier.scale(0.8F))
|
||||
Text(latToString(data.latitude!!))
|
||||
Text(lonToString(data.longitude!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ship(kdataflow : KDataFlow<Double>) {
|
||||
val cog_rad by kdataflow.data.collectAsStateWithLifecycle()
|
||||
Canvas(modifier = Modifier.fillMaxSize().background(Color.Transparent).zIndex(-1F)) {
|
||||
val min_dim = min(size.width, size.height)
|
||||
val true_heading = 0F // LocalTime.now().second * 6.0F
|
||||
val heading = -(true_heading / 360.0 * 2.0 * Math.PI).toFloat()
|
||||
val heading_rotate_matrix = rotMatrix(heading)
|
||||
val compass = forSquareScreen(heading_rotate_matrix * compass_rose, min_dim)
|
||||
drawDesign(compass, this)
|
||||
|
||||
// val cog = (2.0 * true_heading/360 * 2.0 * Math.PI).toFloat()
|
||||
|
||||
val cog = cog_rad.toFloat()
|
||||
Log.d(TAG, "got cog to " + (cog * 180 / Math.PI))
|
||||
val cog_rotate_matrix = rotMatrix(-cog.toFloat() + heading)
|
||||
val cog_pointer = forSquareScreen(cog_rotate_matrix * cog_ident, min_dim)
|
||||
drawDesign(cog_pointer, this)
|
||||
|
||||
val awd = (Math.PI/8*13).toFloat() // (1/2 * Math.PI).toFloat()
|
||||
val awd_rotate_matrix = rotMatrix(-awd + heading)
|
||||
val awd_pointer = forSquareScreen(awd_rotate_matrix * awd_ident, min_dim)
|
||||
drawDesign(awd_pointer, this)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun courseOverGroundTrue(kdataflow : KDataFlow<Double>) {
|
||||
val deg by kdataflow.data.collectAsStateWithLifecycle()
|
||||
val deg360 = round(deg*180.0/Math.PI)
|
||||
Text("COG", Modifier.scale(0.8F))
|
||||
Text("%03.0f°".format(deg360))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun speedOverGround(kdataflow : KDataFlow<Double>) {
|
||||
val spd by kdataflow.data.collectAsStateWithLifecycle()
|
||||
val spd_kn = spd * 1.94384
|
||||
Text("SOG", Modifier.scale(0.8F))
|
||||
Text("%02.1fkn".format(spd_kn))
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Position(kdataflow : KDataFlow<Position>) {
|
||||
val pos by kdataflow.data.collectAsStateWithLifecycle()
|
||||
Column (modifier = Modifier.scale(0.8F), verticalArrangement = Arrangement.Center,Alignment.CenterHorizontally) {
|
||||
|
||||
Text(latToString(pos.latitude))
|
||||
Text(lonToString(pos.longitude))
|
||||
// Text("Boat Position", Modifier.scale(0.8F))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Time(kdataflow : KDataFlow<Instant>) {
|
||||
val time by kdataflow.data.collectAsStateWithLifecycle()
|
||||
val timez = OffsetDateTime.ofInstant(time, ZoneOffset.UTC)
|
||||
val fmt1 = DateTimeFormatter.ofPattern("MMM dd HH:mm")
|
||||
|
||||
Column (modifier = Modifier.scale(0.8F), verticalArrangement = Arrangement.Center,Alignment.CenterHorizontally) {
|
||||
// Text("Boat Time", Modifier.scale(0.8F))
|
||||
Text(timez.format(fmt1))
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Greeting(greetingName: String) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colors.primary,
|
||||
text = stringResource(R.string.hello_world, greetingName)
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(device = Devices.WEAR_OS_SMALL_ROUND, showSystemUi = true)
|
||||
@Composable
|
||||
fun DefaultPreview() {
|
||||
WearAppPreview("Preview Android")
|
||||
}
|
||||
34
app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt
Normal file
34
app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt
Normal file
@@ -0,0 +1,34 @@
|
||||
package net.dalsgaard.wearsignalk.util
|
||||
|
||||
import kotlin.math.floor
|
||||
|
||||
private fun degToString (degrees: Double, dir : Char) : String {
|
||||
|
||||
val deg = floor(degrees)
|
||||
val frac60 = 60 * (degrees - deg)
|
||||
val min = floor(frac60)
|
||||
val frac3600 = 60 * (frac60 - min)
|
||||
val sec = floor(frac3600)
|
||||
|
||||
return "%03.0f°%02.0fʹ%02.0fʺ %s".format(deg, min, sec, dir)
|
||||
}
|
||||
|
||||
public fun latToString (latitude: Double) : String {
|
||||
return if (latitude > 0) {
|
||||
degToString(latitude, 'E')
|
||||
} else if (latitude < 0) {
|
||||
degToString(-latitude, 'W')
|
||||
} else {
|
||||
"000°00ʹ00ʺ -"
|
||||
}
|
||||
}
|
||||
|
||||
public fun lonToString (longitude: Double) : String {
|
||||
return if (longitude > 0) {
|
||||
degToString(longitude, 'N')
|
||||
} else if (longitude < 0) {
|
||||
degToString(-longitude, 'S')
|
||||
} else {
|
||||
"000°00ʹ00ʺ -"
|
||||
}
|
||||
}
|
||||
96
app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt
Normal file
96
app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt
Normal file
@@ -0,0 +1,96 @@
|
||||
package net.dalsgaard.wearsignalk.util
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val compass_rose =
|
||||
Design(listOf(Vector(0.0F, 0.5F), Vector(0.0F, 0.35F)), listOf(Design.Trace(listOf(0, 1), 5.0F, Color.Red))) +
|
||||
Design(listOf(Vector(0.020869565217391303F, 0.2582608695652174F), Vector(0.02086956521739131F, 0.331304347826087F),
|
||||
Vector(0.017391304347826084F, 0.2582608695652174F), Vector(-0.024347826086956518F, 0.3243478260869565F),
|
||||
Vector(0.017391304347826084F, 0.2652173913043478F), Vector(-0.024347826086956518F, 0.331304347826087F),
|
||||
Vector(-0.024347826086956525F, 0.2582608695652174F), Vector(-0.024347826086956518F, 0.331304347826087F),
|
||||
Vector(0.03130434782608695F, 0.2582608695652174F), Vector(0.017391304347826084F, 0.2582608695652174F),
|
||||
Vector(-0.013913043478260875F, 0.2582608695652174F), Vector(-0.03478260869565218F, 0.2582608695652174F),
|
||||
Vector(0.031304347826086966F, 0.3313043478260869F), Vector(0.010434782608695656F, 0.331304347826087F)),
|
||||
listOf(Design.Trace(listOf(0, 1), 2F, Color.Red),
|
||||
Design.Trace(listOf(2, 3), 2F, Color.Red),
|
||||
Design.Trace(listOf(4, 5), 2F, Color.Red),
|
||||
Design.Trace(listOf(6, 7), 2F, Color.Red),
|
||||
Design.Trace(listOf(8, 9), 2F, Color.Red),
|
||||
Design.Trace(listOf(10, 11), 2F, Color.Red),
|
||||
Design.Trace(listOf(12, 13), 2F, Color.Red))) +
|
||||
Design(listOf(Vector(0.5F, 0.0F), Vector(0.35F, 0.0F)), listOf(Design.Trace(listOf(0, 1), 5.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.0F, -0.5F), Vector(0.0F, -0.35F)), listOf(Design.Trace(listOf(0, 1), 5.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.5F, 0.0F), Vector(-0.35F, 0.0F)), listOf(Design.Trace(listOf(0, 1), 5.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.3535533905932738F, 0.35355339059327373F), Vector(0.3005203820042827F, 0.30052038200428266F)), listOf(Design.Trace(listOf(0, 1), 3.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.35355339059327373F, 0.3535533905932738F), Vector(-0.30052038200428266F, 0.3005203820042827F)), listOf(Design.Trace(listOf(0, 1), 3.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.35355339059327384F, -0.35355339059327373F), Vector(-0.30052038200428277F, -0.30052038200428266F)), listOf(Design.Trace(listOf(0, 1), 3.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.3535533905932737F, -0.35355339059327384F), Vector(0.3005203820042826F, -0.30052038200428277F)), listOf(Design.Trace(listOf(0, 1), 3.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.4903926402016152F, 0.09754516100806412F), Vector(0.4413533761814537F, 0.08779064490725771F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.46193976625564337F, 0.1913417161825449F), Vector(0.415745789630079F, 0.1722075445642904F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.4157348061512726F, 0.2777851165098011F), Vector(0.37416132553614534F, 0.250006604858821F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.27778511650980114F, 0.4157348061512726F), Vector(0.25000660485882104F, 0.37416132553614534F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.19134171618254492F, 0.46193976625564337F), Vector(0.17220754456429044F, 0.415745789630079F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.09754516100806417F, 0.4903926402016152F), Vector(0.08779064490725776F, 0.4413533761814537F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.0975451610080641F, 0.4903926402016152F), Vector(-0.08779064490725769F, 0.4413533761814537F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.19134171618254486F, 0.46193976625564337F), Vector(-0.17220754456429038F, 0.415745789630079F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.277785116509801F, 0.41573480615127273F), Vector(-0.25000660485882087F, 0.37416132553614545F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.4157348061512727F, 0.2777851165098011F), Vector(-0.3741613255361454F, 0.250006604858821F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.46193976625564337F, 0.19134171618254495F), Vector(-0.415745789630079F, 0.17220754456429047F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.4903926402016152F, 0.0975451610080643F), Vector(-0.4413533761814537F, 0.08779064490725788F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.4903926402016152F, -0.09754516100806418F), Vector(-0.4413533761814537F, -0.08779064490725777F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.4619397662556434F, -0.19134171618254484F), Vector(-0.4157457896300791F, -0.17220754456429035F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.41573480615127273F, -0.277785116509801F), Vector(-0.37416132553614545F, -0.25000660485882087F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.2777851165098011F, -0.4157348061512726F), Vector(-0.250006604858821F, -0.37416132553614534F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.19134171618254517F, -0.46193976625564326F), Vector(-0.17220754456429066F, -0.41574578963007897F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(-0.09754516100806433F, -0.49039264020161516F), Vector(-0.0877906449072579F, -0.44135337618145365F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.09754516100806415F, -0.4903926402016152F), Vector(0.08779064490725774F, -0.4413533761814537F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.191341716182545F, -0.4619397662556433F), Vector(0.1722075445642905F, -0.41574578963007897F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.2777851165098009F, -0.41573480615127273F), Vector(0.2500066048588208F, -0.37416132553614545F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.4157348061512726F, -0.2777851165098011F), Vector(0.37416132553614534F, -0.250006604858821F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.46193976625564326F, -0.1913417161825452F), Vector(0.41574578963007897F, -0.1722075445642907F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(listOf(Vector(0.49039264020161516F, -0.09754516100806436F), Vector(0.44135337618145365F, -0.08779064490725792F)), listOf(Design.Trace(listOf(0, 1), 1.0F, Color.Gray))) +
|
||||
Design(emptyList(), emptyList())
|
||||
|
||||
val cog_ident_color = Color(0xAD, 0xD8, 0xE6)
|
||||
val cog_ident =
|
||||
Design(listOf(Vector(0.0F, 0.5F), Vector(0.0F, 0.4F),
|
||||
Vector(-0.009937500000000002F, 0.3125F), Vector(-0.013062500000000001F, 0.3109375F), Vector(-0.016187500000000004F, 0.3078125F), Vector(-0.017750000000000005F, 0.3046875F), Vector(-0.017750000000000005F, 0.29843749999999997F), Vector(-0.016187500000000004F, 0.2953125F), Vector(-0.013062500000000005F, 0.2921875F), Vector(-0.009937500000000005F, 0.29062499999999997F), Vector(-0.005250000000000004F, 0.2890625F), Vector(0.002562499999999996F, 0.2890625F), Vector(0.007249999999999997F, 0.29062499999999997F), Vector(0.010374999999999999F, 0.2921875F), Vector(0.013499999999999998F, 0.2953125F), Vector(0.015062499999999996F, 0.29843749999999997F), Vector(0.015062499999999996F, 0.3046875F), Vector(0.013499999999999998F, 0.3078125F), Vector(0.010374999999999999F, 0.3109375F), Vector(0.007249999999999998F, 0.3125F),
|
||||
Vector(-0.017750000000000002F, 0.32968749999999997F), Vector(-0.0161875F, 0.3265625F), Vector(-0.013062500000000001F, 0.3234375F), Vector(-0.009937500000000002F, 0.32187499999999997F), Vector(-0.005250000000000002F, 0.3203125F), Vector(0.0025624999999999984F, 0.3203125F), Vector(0.007249999999999999F, 0.32187499999999997F), Vector(0.010374999999999999F, 0.3234375F), Vector(0.013499999999999998F, 0.3265625F), Vector(0.0150625F, 0.32968749999999997F), Vector(0.0150625F, 0.3359375F), Vector(0.013500000000000002F, 0.3390625F), Vector(0.010375000000000002F, 0.3421875F), Vector(0.00725F, 0.34375F), Vector(0.0025624999999999997F, 0.34531249999999997F), Vector(-0.00525F, 0.34531249999999997F), Vector(-0.009937500000000002F, 0.34375F), Vector(-0.013062500000000001F, 0.3421875F), Vector(-0.0161875F, 0.3390625F), Vector(-0.017750000000000002F, 0.3359375F), Vector(-0.017750000000000002F, 0.32968749999999997F),
|
||||
Vector(-0.009937499999999998F, 0.37968749999999996F), Vector(-0.013062499999999998F, 0.378125F), Vector(-0.0161875F, 0.375F), Vector(-0.017750000000000002F, 0.37187499999999996F), Vector(-0.017750000000000002F, 0.365625F), Vector(-0.0161875F, 0.3625F), Vector(-0.013062500000000001F, 0.359375F), Vector(-0.009937500000000002F, 0.3578125F), Vector(-0.00525F, 0.35624999999999996F), Vector(0.0025625000000000005F, 0.35624999999999996F), Vector(0.007250000000000001F, 0.3578125F), Vector(0.010375000000000002F, 0.359375F), Vector(0.013500000000000002F, 0.3625F), Vector(0.015062500000000003F, 0.365625F), Vector(0.015062500000000003F, 0.37187499999999996F), Vector(0.013500000000000002F, 0.375F), Vector(0.010375000000000002F, 0.378125F), Vector(0.007250000000000002F, 0.37968749999999996F), Vector(0.002562500000000002F, 0.37968749999999996F),
|
||||
Vector(0.0025625000000000014F, 0.37187499999999996F), Vector(0.002562500000000002F, 0.37968749999999996F)),
|
||||
listOf(Design.Trace(listOf(0, 1), 1.0F, cog_ident_color),
|
||||
Design.Trace(listOf(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19), 1.0F, cog_ident_color),
|
||||
Design.Trace(listOf(20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40), 1.0F, cog_ident_color),
|
||||
Design.Trace(listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59), 1.0F, cog_ident_color),
|
||||
Design.Trace(listOf(60, 61), 1.0F, cog_ident_color)))
|
||||
|
||||
val awd_ident_color = Color (0xFF, 0xF7, 0x00)
|
||||
val awd_ident =
|
||||
Design(listOf(Vector(0.0F, 0.5F), Vector(0.0F, 0.4F),
|
||||
Vector(-0.018047619047619052F, 0.3F), Vector(0.015285714285714281F, 0.2873015873015873F),
|
||||
Vector(-0.018047619047619052F, 0.3F), Vector(0.015285714285714284F, 0.3126984126984127F),
|
||||
Vector(0.004174603174603171F, 0.292063492063492F), Vector(0.004174603174603172F, 0.3079365079365079F),
|
||||
Vector(-0.018047619047619052F, 0.3126984126984127F), Vector(0.015285714285714284F, 0.3206349206349206F),
|
||||
Vector(-0.01804761904761905F, 0.32857142857142857F), Vector(0.015285714285714284F, 0.3206349206349206F),
|
||||
Vector(-0.01804761904761905F, 0.32857142857142857F), Vector(0.015285714285714284F, 0.3365079365079365F),
|
||||
Vector(-0.01804761904761905F, 0.34444444444444444F), Vector(0.015285714285714284F, 0.3365079365079365F),
|
||||
Vector(-0.01804761904761905F, 0.3555555555555555F), Vector(0.015285714285714284F, 0.3555555555555555F),
|
||||
Vector(-0.01804761904761905F, 0.3555555555555555F), Vector(-0.01804761904761905F, 0.36666666666666664F), Vector(-0.01646031746031746F, 0.3714285714285714F), Vector(-0.013285714285714283F, 0.3746031746031746F), Vector(-0.010111111111111109F, 0.3761904761904762F), Vector(-0.0053492063492063474F, 0.37777777777777777F), Vector(0.002587301587301589F, 0.37777777777777777F), Vector(0.007349206349206351F, 0.3761904761904762F), Vector(0.010523809523809526F, 0.3746031746031746F), Vector(0.0136984126984127F, 0.3714285714285714F), Vector(0.015285714285714288F, 0.36666666666666664F), Vector(0.015285714285714284F, 0.3555555555555555F)),
|
||||
listOf(Design.Trace(listOf(0, 1), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(2, 3), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(4, 5), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(6, 7), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(8, 9), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(10, 11), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(12, 13), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(14, 15), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(16, 17), 1.0F, awd_ident_color),
|
||||
Design.Trace(listOf(18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29), 1.0F, awd_ident_color)))
|
||||
|
||||
val ship_color=Color(0x80, 0x85, 0x88)
|
||||
val ship = Design(listOf(Vector(-0.008872458410351202F, -0.4F), Vector(-0.022181146025878007F, -0.39556377079482447F), Vector(-0.03844731977818854F, -0.38669131238447324F), Vector(-0.047319778188539746F, -0.37634011090573016F), Vector(-0.0857670979667283F, -0.24916820702402961F), Vector(-0.10646950092421442F, -0.1604436229205176F), Vector(-0.11386321626617377F, -0.10425138632162663F), Vector(-0.1168207024029575F, -0.04066543438077634F), Vector(-0.11534195933456563F, -0.0007393715341959335F), Vector(-0.11090573012939003F, 0.05841035120147875F), Vector(-0.10055452865064696F, 0.11608133086876156F), Vector(-0.08428835489833643F, 0.18558225508317933F), Vector(-0.051756007393715345F, 0.28170055452865067F), Vector(-0.011829944547134937F, 0.3748613678373383F), Vector(-0.001478743068391867F, 0.4F), Vector(0.001478743068391867F, 0.4F), Vector(0.011829944547134937F, 0.3748613678373383F), Vector(0.051756007393715345F, 0.28170055452865067F), Vector(0.08428835489833643F, 0.18558225508317933F), Vector(0.10055452865064696F, 0.11608133086876156F), Vector(0.11090573012939003F, 0.05841035120147875F), Vector(0.11534195933456563F, -0.0007393715341959335F), Vector(0.1168207024029575F, -0.04066543438077634F), Vector(0.11386321626617377F, -0.10425138632162663F), Vector(0.10646950092421442F, -0.1604436229205176F), Vector(0.0857670979667283F, -0.24916820702402961F), Vector(0.047319778188539746F, -0.37634011090573016F), Vector(0.03844731977818854F, -0.38669131238447324F), Vector(0.022181146025878007F, -0.39556377079482447F), Vector(0.008872458410351202F, -0.4F), Vector(-0.047319778188539746F, -0.37634011090573016F), Vector(-0.031053604436229208F, -0.3792975970425139F), Vector(-0.01478743068391867F, -0.38225508317929763F), Vector(0.01478743068391867F, -0.38225508317929763F), Vector(0.031053604436229208F, -0.3792975970425139F), Vector(0.047319778188539746F, -0.37634011090573016F), Vector(0.0F, -0.3127541589648799F), Vector(-0.002957486136783734F, -0.30683918669131244F), Vector(-0.001478743068391867F, -0.300924214417745F), Vector(0.0F, -0.22994454713493534F), Vector(0.001478743068391867F, -0.300924214417745F), Vector(0.002957486136783734F, -0.30683918669131244F), Vector(-0.002957486136783734F, -0.2935304990757856F), Vector(-0.03992606284658041F, -0.29500924214417745F), Vector(-0.05027726432532348F, -0.2905730129390019F), Vector(-0.05619223659889095F, -0.2802218114602588F), Vector(-0.062107208872458415F, -0.25951940850277266F), Vector(-0.06802218114602589F, -0.22107208872458411F), Vector(-0.07541589648798522F, -0.12643253234750462F), Vector(0.07541589648798522F, -0.12643253234750462F), Vector(0.06802218114602589F, -0.22107208872458411F), Vector(0.062107208872458415F, -0.25951940850277266F), Vector(0.05619223659889095F, -0.2802218114602588F), Vector(0.05027726432532348F, -0.2905730129390019F), Vector(0.03992606284658041F, -0.29500924214417745F), Vector(0.002957486136783734F, -0.2935304990757856F), Vector(-0.07245841035120149F, -0.12643253234750462F), Vector(-0.07245841035120149F, -0.01700554528650647F), Vector(-0.06802218114602589F, 0.042144177449168214F), Vector(-0.05914972273567468F, 0.09981515711645103F), Vector(-0.04584103512014788F, 0.15452865064695012F), Vector(-0.02957486136783734F, 0.21072088724584107F), Vector(-0.026617375231053605F, 0.21663585951940853F), Vector(-0.01922365988909427F, 0.21959334565619226F), Vector(-0.008872458410351202F, 0.22107208872458411F), Vector(0.008872458410351202F, 0.22107208872458411F), Vector(0.01922365988909427F, 0.21959334565619226F), Vector(0.026617375231053605F, 0.21663585951940853F), Vector(0.02957486136783734F, 0.21072088724584107F), Vector(0.04584103512014788F, 0.15452865064695012F), Vector(0.05914972273567468F, 0.09981515711645103F), Vector(0.06802218114602589F, 0.042144177449168214F), Vector(0.07245841035120149F, -0.01700554528650647F), Vector(0.07245841035120149F, -0.12643253234750462F)), listOf(Design.Trace(listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0), 2.0F, ship_color),
|
||||
Design.Trace(listOf(30, 31, 32, 33, 34, 35), 2.0F, ship_color),
|
||||
Design.Trace(listOf(36, 37, 38, 39, 40, 41, 36), 2.0F, ship_color),
|
||||
Design.Trace(listOf(42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 42), 2.0F, ship_color),
|
||||
Design.Trace(listOf(56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73), 2.0F, ship_color)))
|
||||
|
||||
26
app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt
Normal file
26
app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt
Normal file
@@ -0,0 +1,26 @@
|
||||
package net.dalsgaard.wearsignalk.util
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
data class Glyph (val points: List<List<Vector>>, val left: Float, val right: Float)
|
||||
|
||||
fun glyphJoin(glyphs: List<Glyph>, stroke: Float, color: Color) : Design
|
||||
{
|
||||
if (glyphs.size == 0) { return Design(emptyList(), emptyList()) }
|
||||
|
||||
// rearrange....
|
||||
var left_extend=0F
|
||||
val d_points = mutableListOf<Vector>()
|
||||
val d_indices = mutableListOf<List<Int>>()
|
||||
glyphs.forEach {
|
||||
val move = Vector(left_extend - it.left, 0F)
|
||||
it.points.forEach {
|
||||
d_indices.add((d_points.size..(d_points.size + it.size)).toList())
|
||||
d_points.addAll (move + it)
|
||||
}
|
||||
left_extend = left_extend + (it.right - it.left)
|
||||
}
|
||||
val move_back = Vector(-left_extend/2, 0F)
|
||||
return Design(move_back + d_points, d_indices.map { Design.Trace(it, stroke, color)})
|
||||
}
|
||||
|
||||
126
app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt
Normal file
126
app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt
Normal file
@@ -0,0 +1,126 @@
|
||||
package net.dalsgaard.wearsignalk.util
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* Model for a 'Design' - which is a list of points in the 2D space
|
||||
* along information about which points are to be connected with lines
|
||||
* of certain width and color.
|
||||
*/
|
||||
class Design (val points: List<Vector>, val traces: List<Trace>) {
|
||||
data class Trace(val indices: List<Int>, val width: Float, val color: Color)
|
||||
data class BoundingBox(val x0: Float, val y0: Float, val x1: Float, val y1: Float)
|
||||
|
||||
operator fun plus (o: Design) : Design {
|
||||
return Design (points + o.points, traces + o.traces.map {
|
||||
Trace(it.indices.map { it + points.size }, it.width, it.color)
|
||||
})
|
||||
}
|
||||
|
||||
operator fun plus (o: Vector) : Design {
|
||||
return Design (o + points, traces)
|
||||
}
|
||||
|
||||
fun boundingBox () : BoundingBox {
|
||||
return points.fold (BoundingBox(Float.MAX_VALUE, Float.MAX_VALUE, Float.MIN_VALUE, Float.MIN_VALUE)) { acc, nxt ->
|
||||
BoundingBox(
|
||||
min(nxt.x, acc.x0),
|
||||
min(nxt.y, acc.y0),
|
||||
max(nxt.x, acc.x1),
|
||||
max(nxt.y, acc.y1)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a 1 by 1 design centered on (0,0) onto a
|
||||
* square screen with inverted Y.
|
||||
*/
|
||||
fun forSquareScreen (d: Design, edge: Float) : Design {
|
||||
return scaleMatrix(edge) * (Matrix(1f, 0f, 0f, -1f)*d + Vector(0.5f, 0.5f))
|
||||
}
|
||||
|
||||
/**
|
||||
* Model for a 2D vector, along with basic operators
|
||||
*
|
||||
*/
|
||||
data class Vector (val x: Float, val y: Float) {
|
||||
|
||||
operator fun plus (o: Vector) : Vector {
|
||||
return Vector (x+o.x, y+o.y)
|
||||
}
|
||||
|
||||
operator fun minus (o: Vector) : Vector {
|
||||
return Vector (x-o.x, y-o.y)
|
||||
}
|
||||
|
||||
operator fun plus (o: List<Vector>) : List<Vector> {
|
||||
return o.map {
|
||||
Vector(x+it.x, y+it.y)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Model for a 2x2 matrix, along with a fairly extensive
|
||||
* set of operators not only for matrices and vectors, but also
|
||||
* for list of vectors and 'Designs'.
|
||||
*/
|
||||
data class Matrix (val a: Float, val b: Float, val c: Float, val d: Float) {
|
||||
|
||||
operator fun plus (o: Matrix) : Matrix {
|
||||
return Matrix(a+o.a, b+o.b, c+o.c, d+o.d)
|
||||
}
|
||||
|
||||
operator fun minus (o: Matrix) : Matrix {
|
||||
return Matrix(a-o.a, b-o.b, c-o.c, d-o.d)
|
||||
}
|
||||
|
||||
operator fun times (o: Matrix) : Matrix {
|
||||
return Matrix(a*o.a + b*o.c, a*o.b + b*o.d, c*o.a + d*o.c, c*o.b + d*o.d)
|
||||
}
|
||||
|
||||
operator fun times (o: Float) : Matrix {
|
||||
return Matrix(a*o, b*o, c*o, d*o)
|
||||
}
|
||||
|
||||
operator fun times (o: List<Vector>) : List<Vector> {
|
||||
return o.map {
|
||||
Vector(a*it.x + b*it.y, c*it.x + d*it.y)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun times (o: Design) : Design {
|
||||
return Design(this * o.points, o.traces)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns 2x2 matrix for rotation in the plane, angle given in radians.
|
||||
*/
|
||||
fun rotMatrix(theta: Float) : Matrix {
|
||||
return Matrix(cos(theta), -sin(theta), sin(theta), cos(theta))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identity matrix.
|
||||
*/
|
||||
fun eyeMatrix() : Matrix {
|
||||
return Matrix(1f, 0f, 0f, 1f)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a scaling matrix,
|
||||
* basically identical to: eyeMatrix()*factor.
|
||||
*/
|
||||
fun scaleMatrix(factor: Float) : Matrix {
|
||||
return Matrix(factor, 0f, 0f, factor)
|
||||
}
|
||||
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
3
app/src/main/res/values-round/strings.xml
Normal file
3
app/src/main/res/values-round/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="hello_world">From the Round world,\nHello, %1$s!</string>
|
||||
</resources>
|
||||
8
app/src/main/res/values/strings.xml
Normal file
8
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
<string name="app_name">WearSignalK</string>
|
||||
<!--
|
||||
This string is used for square devices and overridden by hello_world in
|
||||
values-round/strings.xml for round devices.
|
||||
-->
|
||||
<string name="hello_world">From the Square world,\nHello, %1$s!</string>
|
||||
</resources>
|
||||
5
build.gradle.kts
Normal file
5
build.gradle.kts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
|
||||
}
|
||||
24
gradle.properties
Normal file
24
gradle.properties
Normal file
@@ -0,0 +1,24 @@
|
||||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
||||
|
||||
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
#Sat Oct 07 23:54:01 CEST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
18
settings.gradle.kts
Normal file
18
settings.gradle.kts
Normal file
@@ -0,0 +1,18 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "WearSignalK"
|
||||
include(":app")
|
||||
|
||||
Reference in New Issue
Block a user