From 0f30f0206f13554d60091d272fc35cf5954cbe01 Mon Sep 17 00:00:00 2001 From: Jakob Dalsgaard Date: Wed, 22 Nov 2023 13:43:26 +0100 Subject: [PATCH] First Commit of WearSignalK2 --- app/build.gradle.kts | 81 ++++++ app/proguard-rules.pro | 21 ++ app/src/main/AndroidManifest.xml | 41 +++ .../connection/SignalKConnection.kt | 215 +++++++++++++++ .../wearsignalk/presentation/MainActivity.kt | 254 ++++++++++++++++++ .../net/dalsgaard/wearsignalk/util/degrees.kt | 34 +++ .../net/dalsgaard/wearsignalk/util/design.kt | 96 +++++++ .../net/dalsgaard/wearsignalk/util/font.kt | 26 ++ .../net/dalsgaard/wearsignalk/util/matrix.kt | 126 +++++++++ app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes app/src/main/res/values-round/strings.xml | 3 + app/src/main/res/values/strings.xml | 8 + build.gradle.kts | 5 + gradle.properties | 24 ++ gradle/wrapper/gradle-wrapper.properties | 6 + settings.gradle.kts | 18 ++ 20 files changed, 958 insertions(+) create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/connection/SignalKConnection.kt create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/presentation/MainActivity.kt create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt create mode 100644 app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/values-round/strings.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 settings.gradle.kts diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..ad4cfc2 --- /dev/null +++ b/app/build.gradle.kts @@ -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") + +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..8d52564 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/connection/SignalKConnection.kt b/app/src/main/java/net/dalsgaard/wearsignalk/connection/SignalKConnection.kt new file mode 100644 index 0000000..b749860 --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/connection/SignalKConnection.kt @@ -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(inflow: Channel, default: T): ViewModel() { + val data = flow { while(true) { emit(inflow.receive()) } } + .stateIn(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) : SignalKMapper { + override suspend fun update(json: JsonNode): Boolean { + val tstamputc = json?.read("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("value.latitude"), json?.read("value.longitude")) +/* + data.nextPointLatitude = point?.first + data.nextPointLongitude = point?.second + + data.nextPointBearingTrue = json?.read("bearingTrue.value") + data.nextPointVMG = json?.read("velocityMadeGood.value") + data.nextPointDistance = json?.read("distance.value") +*/ + return true + } +} + +class NavigationPositionMapper(val outflow: Channel) : SignalKMapper { + override suspend fun update(json: JsonNode): Boolean { + val point = Pair(json?.read("value.latitude"), json?.read("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) : SignalKMapper { + override suspend fun update(json: JsonNode): Boolean { + val speedOverGround = json?.read("value") + if (speedOverGround != null) outflow.send(speedOverGround) + return true + } +} + +class NavigationCourseOverGroundMapper(val outflow: Channel) : SignalKMapper { + override suspend fun update(json: JsonNode): Boolean { + val courseOverGroundTrue = json?.read("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() + val datetimeChannel = Channel() + val courseOverGroundTrueChannel = Channel() + val speedOverGroundChannel = Channel() + + private val signalKmapper : Map + 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 { + return KDataFlow(navigationPositionChannel, Position()) + } + + public fun getFlowDatetime () : KDataFlow { + return KDataFlow(datetimeChannel, Instant.ofEpochMilli(0)) + } + + public fun getFlowCourseOverGround () : KDataFlow { + return KDataFlow(courseOverGroundTrueChannel, 0.0) + } + + public fun getFlowSpeedOverGround () : KDataFlow { + 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>("$.updates[*].values[*]") + if (updates != null) { + Log.d(TAG, "Got updates") + for (i in updates) { + val path = i.read("path") + signalKmapper[path]?.update(i) + } + } + // Log.d(TAG, "data is now: $data") + } + Log.d(TAG, "closing websocket") + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/presentation/MainActivity.kt b/app/src/main/java/net/dalsgaard/wearsignalk/presentation/MainActivity.kt new file mode 100644 index 0000000..a82d4b5 --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/presentation/MainActivity.kt @@ -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) { + 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) { + 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) { + 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) { + 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) { + 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") +} \ No newline at end of file diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt b/app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt new file mode 100644 index 0000000..104bed7 --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/util/degrees.kt @@ -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ʺ -" + } +} diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt b/app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt new file mode 100644 index 0000000..476f8d3 --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/util/design.kt @@ -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))) + diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt b/app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt new file mode 100644 index 0000000..9b85abe --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/util/font.kt @@ -0,0 +1,26 @@ +package net.dalsgaard.wearsignalk.util + +import androidx.compose.ui.graphics.Color + +data class Glyph (val points: List>, val left: Float, val right: Float) + +fun glyphJoin(glyphs: List, stroke: Float, color: Color) : Design +{ + if (glyphs.size == 0) { return Design(emptyList(), emptyList()) } + + // rearrange.... + var left_extend=0F + val d_points = mutableListOf() + val d_indices = mutableListOf>() + 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)}) +} + diff --git a/app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt b/app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt new file mode 100644 index 0000000..3da0ae8 --- /dev/null +++ b/app/src/main/java/net/dalsgaard/wearsignalk/util/matrix.kt @@ -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, val traces: List) { + data class Trace(val indices: List, 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) : List { + 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) : List { + 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) +} + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..4f0f1d64e58ba64d180ce43ee13bf9a17835fbca GIT binary patch literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!To6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..28d4b77f9f036a47549d47db79c16788749dca10 GIT binary patch literal 2884 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 + From the Round world,\nHello, %1$s! + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..d016792 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ + + WearSignalK + + From the Square world,\nHello, %1$s! + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..b4cf450 --- /dev/null +++ b/build.gradle.kts @@ -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 +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..8ea0209 --- /dev/null +++ b/gradle.properties @@ -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 + diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..83863e3 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..c16357b --- /dev/null +++ b/settings.gradle.kts @@ -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") + \ No newline at end of file