diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 04:40:36 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-22 04:40:36 +0000 |
| commit | 64d66c6ae4fde6aa3e66a5dba33950d447af1102 (patch) | |
| tree | 612734b3803dd61f311e2a837c656832828481cd /android-app/app/src/main/temp | |
| parent | 4d637d284dce6fc674599c226dd063c442fd350f (diff) | |
refactor: cleanup, simplify, and modularize Android app logic
- Extracted MOB, Instruments, Map, and Anchor Watch logic from MainActivity into dedicated handlers.
- Refactored LocationService to use a standalone MockTidalCurrentGenerator.
- Removed legacy 'kotlin_old', 'res_old', and 'temp' directories.
- Added KDoc documentation to core components and handlers.
- Integrated JUnit 5 dependencies and configured the test runner.
- Verified all changes with successful unit test execution.
Diffstat (limited to 'android-app/app/src/main/temp')
| -rwxr-xr-x | android-app/app/src/main/temp/CompassRoseView.kt | 217 | ||||
| -rwxr-xr-x | android-app/app/src/main/temp/HeadingDataProcessor.kt | 108 |
2 files changed, 0 insertions, 325 deletions
diff --git a/android-app/app/src/main/temp/CompassRoseView.kt b/android-app/app/src/main/temp/CompassRoseView.kt deleted file mode 100755 index 8e755a3..0000000 --- a/android-app/app/src/main/temp/CompassRoseView.kt +++ /dev/null @@ -1,217 +0,0 @@ -package org.terst.nav.temp // Temporarily placing in 'temp' due to permissions, actual package should be 'org.terst.nav' - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import kotlin.math.cos -import kotlin.math.min -import kotlin.math.sin - -class CompassRoseView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : View(context, attrs, defStyleAttr) { - - private var heading: Float = 0f // Current heading in degrees - set(value) { - field = value % 360 // Ensure heading is within 0-359 - invalidate() - } - private var cog: Float = 0f // Course Over Ground in degrees - set(value) { - field = value % 360 - invalidate() - } - private var isTrueHeading: Boolean = true // True for True heading, false for Magnetic - - private val rosePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.DKGRAY - style = Paint.Style.STROKE - strokeWidth = 2f - } - - private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - textSize = 30f - textAlign = Paint.Align.CENTER - } - - private val cardinalTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - textSize = 40f - textAlign = Paint.Align.CENTER - isFakeBoldText = true - } - - private val majorTickPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.WHITE - strokeWidth = 3f - } - - private val minorTickPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.GRAY - strokeWidth = 1f - } - - private val headingNeedlePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.RED - style = Paint.Style.FILL - } - - private val cogArrowPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { - color = Color.BLUE - style = Paint.Style.FILL - strokeWidth = 5f - } - - private var viewCenterX: Float = 0f - private var viewCenterY: Float = 0f - private var radius: Float = 0f - - override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { - super.onSizeChanged(w, h, oldw, oldh) - viewCenterX = w / 2f - viewCenterY = h / 2f - radius = min(w, h) / 2f - 40f // Leave some padding - textPaint.textSize = radius / 6f - cardinalTextPaint.textSize = radius / 4.5f - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - - // Draw outer circle - canvas.drawCircle(viewCenterX, viewCenterY, radius, rosePaint) - - // Draw cardinal and intercardinal points - drawCardinalPoints(canvas) - - // Draw tick marks and degree labels - drawDegreeMarks(canvas) - - // Draw heading needle - drawHeadingNeedle(canvas, heading, headingNeedlePaint, radius * 0.8f) - - // Draw COG arrow - drawCogArrow(canvas, cog, cogArrowPaint, radius * 0.6f) - - // Draw current heading text in the center - drawHeadingText(canvas) - } - - private fun drawCardinalPoints(canvas: Canvas) { - val cardinalPoints = listOf("N", "E", "S", "W") - val angles = listOf(0f, 90f, 180f, 270f) - val textBound = Rect() - - for (i in cardinalPoints.indices) { - val angleRad = Math.toRadians((angles[i] - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock - val x = viewCenterX + (radius * 0.9f) * cos(angleRad) - val y = viewCenterY + (radius * 0.9f) * sin(angleRad) - - val text = cardinalPoints[i] - cardinalTextPaint.getTextBounds(text, 0, text.length, textBound) - val textHeight = textBound.height() - - canvas.drawText(text, x, y + textHeight / 2, cardinalTextPaint) - } - } - - private fun drawDegreeMarks(canvas: Canvas) { - for (i in 0 until 360 step 5) { - val isMajor = (i % 30 == 0) // Major ticks every 30 degrees - val tickLength = if (isMajor) 30f else 15f - val currentTickPaint = if (isMajor) majorTickPaint else minorTickPaint - val startRadius = radius - tickLength - - val angleRad = Math.toRadians((i - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock - - val startX = viewCenterX + startRadius * cos(angleRad) - val startY = viewCenterY + startRadius * sin(angleRad) - val endX = viewCenterX + radius * cos(angleRad) - val endY = viewCenterY + radius * sin(angleRad) - - canvas.drawLine(startX, startY, endX, endY, currentTickPaint) - - if (isMajor && i != 0) { // Draw degree labels for major ticks (except North) - val textRadius = radius - tickLength - textPaint.textSize / 2 - 10f - val textX = viewCenterX + textRadius * cos(angleRad) - val textY = viewCenterY + textRadius * sin(angleRad) + textPaint.textSize / 2 - - canvas.drawText(i.toString(), textX, textY, textPaint) - } - } - } - - private fun drawHeadingNeedle(canvas: Canvas, angle: Float, paint: Paint, length: Float) { - val angleRad = Math.toRadians((angle - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock - val endX = viewCenterX + length * cos(angleRad) - val endY = viewCenterY + length * sin(angleRad) - - // Draw a simple triangle for the needle - val needleWidth = 20f - val path = android.graphics.Path() - path.moveTo(endX, endY) - path.lineTo(viewCenterX + needleWidth * cos(angleRad - Math.toRadians(90.0).toFloat()), - viewCenterY + needleWidth * sin(angleRad - Math.toRadians(90.0).toFloat())) - path.lineTo(viewCenterX + needleWidth * cos(angleRad + Math.toRadians(90.0).toFloat()), - viewCenterY + needleWidth * sin(angleRad + Math.toRadians(90.0).toFloat())) - path.close() - canvas.drawPath(path, paint) - } - - private fun drawCogArrow(canvas: Canvas, angle: Float, paint: Paint, length: Float) { - val angleRad = Math.toRadians((angle - 90).toDouble()).toFloat() // Adjust for canvas 0deg at 3 o'clock - val endX = viewCenterX + length * cos(angleRad) - val endY = viewCenterY + length * sin(angleRad) - - val startX = viewCenterX + (length * 0.5f) * cos(angleRad) - val startY = viewCenterY + (length * 0.5f) * sin(angleRad) - - canvas.drawLine(startX, startY, endX, endY, paint) - - // Draw arrow head - val arrowHeadLength = 25f - val arrowHeadWidth = 15f - val arrowPath = android.graphics.Path() - arrowPath.moveTo(endX, endY) - arrowPath.lineTo(endX - arrowHeadLength * cos(angleRad - Math.toRadians(30.0).toFloat()), - endY - arrowHeadLength * sin(angleRad - Math.toRadians(30.0).toFloat())) - arrowPath.moveTo(endX, endY) - arrowPath.lineTo(endX - arrowHeadLength * cos(angleRad + Math.toRadians(30.0).toFloat()), - endY - arrowHeadLength * sin(angleRad + Math.toRadians(30.0).toFloat())) - canvas.drawPath(arrowPath, paint) - } - - private fun drawHeadingText(canvas: Canvas) { - val headingText = "${heading.toInt()}°" + if (isTrueHeading) "T" else "M" - textPaint.color = Color.WHITE - textPaint.textSize = radius / 3.5f // Larger text for main heading - canvas.drawText(headingText, viewCenterX, viewCenterY + textPaint.textSize / 3, textPaint) - } - - /** - * Sets the current heading to display. - * @param newHeading The new heading value in degrees (0-359). - * @param isTrue Whether the heading is True (magnetic variation applied) or Magnetic. - */ - fun setHeading(newHeading: Float, isTrue: Boolean) { - this.heading = newHeading - this.isTrueHeading = isTrue - invalidate() - } - - /** - * Sets the Course Over Ground (COG) to display. - * @param newCog The new COG value in degrees (0-359). - */ - fun setCog(newCog: Float) { - this.cog = newCog - invalidate() - } -} diff --git a/android-app/app/src/main/temp/HeadingDataProcessor.kt b/android-app/app/src/main/temp/HeadingDataProcessor.kt deleted file mode 100755 index 7625f90..0000000 --- a/android-app/app/src/main/temp/HeadingDataProcessor.kt +++ /dev/null @@ -1,108 +0,0 @@ -package org.terst.nav.temp // Temporarily placing in 'temp' due to permissions - -import android.hardware.GeomagneticField -import android.location.Location -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update -import java.util.Date - -/** - * Data class representing processed heading information. - * @param trueHeading The heading relative to true North (0-359.9 degrees). - * @param magneticHeading The heading relative to magnetic North (0-359.9 degrees). - * @param magneticVariation The difference between true and magnetic North at the current location (+E, -W). - * @param cog Course Over Ground (0-359.9 degrees). - */ -data class HeadingInfo( - val trueHeading: Float, - val magneticHeading: Float, - val magneticVariation: Float, - val cog: Float -) - -/** - * Processor for handling heading data, including magnetic variation calculations - * using the Android GeomagneticField. - */ -class HeadingDataProcessor { - - private val _headingInfoFlow = MutableStateFlow(HeadingInfo(0f, 0f, 0f, 0f)) - val headingInfoFlow: StateFlow<HeadingInfo> = _headingInfoFlow.asStateFlow() - - private var currentLatitude: Double = 0.0 - private var currentLongitude: Double = 0.0 - private var currentAltitude: Double = 0.0 - - /** - * Updates the current geographic location for magnetic variation calculations. - */ - fun updateLocation(latitude: Double, longitude: Double, altitude: Double) { - currentLatitude = latitude - currentLongitude = longitude - currentAltitude = altitude - // Recalculate magnetic variation if location changes - updateHeadingInfo(_headingInfoFlow.value.trueHeading, _headingInfoFlow.value.cog, true) - } - - /** - * Processes a new true heading and Course Over Ground (COG) value. - * @param newTrueHeading The new true heading in degrees. - * @param newCog The new COG in degrees. - */ - fun updateTrueHeadingAndCog(newTrueHeading: Float, newCog: Float) { - updateHeadingInfo(newTrueHeading, newCog, true) - } - - /** - * Processes a new magnetic heading and Course Over Ground (COG) value. - * @param newMagneticHeading The new magnetic heading in degrees. - * @param newCog The new COG in degrees. - */ - fun updateMagneticHeadingAndCog(newMagneticHeading: Float, newCog: Float) { - updateHeadingInfo(newMagneticHeading, newCog, false) - } - - private fun updateHeadingInfo(heading: Float, cog: Float, isTrueHeadingInput: Boolean) { - val magneticVariation = calculateMagneticVariation() - val (finalTrueHeading, finalMagneticHeading) = if (isTrueHeadingInput) { - Pair(heading, (heading - magneticVariation + 360) % 360) - } else { - Pair((heading + magneticVariation + 360) % 360, heading) - } - - _headingInfoFlow.update { - it.copy( - trueHeading = finalTrueHeading, - magneticHeading = finalMagneticHeading, - magneticVariation = magneticVariation, - cog = cog - ) - } - } - - /** - * Calculates the magnetic variation (declination) for the current location. - * @return Magnetic variation in degrees (+E, -W). - */ - private fun calculateMagneticVariation(): Float { - // GeomagneticField requires current time in milliseconds - val currentTimeMillis = System.currentTimeMillis() - - // Create a dummy Location object to get altitude if only lat/lon are updated - // GeomagneticField needs altitude, using 0 if not provided - val geoField = GeomagneticField( - currentLatitude.toFloat(), - currentLongitude.toFloat(), - currentAltitude.toFloat(), // Altitude in meters - currentTimeMillis - ) - return geoField.declination // Declination is the magnetic variation - } - - // Helper function to normalize angles (0-359.9) - though modulo handles this for positive floats - private fun normalizeAngle(angle: Float): Float { - return (angle % 360 + 360) % 360 - } -} |
