diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/Screenshot_1.png b/Screenshot_1.png new file mode 100644 index 0000000..da500eb Binary files /dev/null and b/Screenshot_1.png differ diff --git a/Screenshot_2.png b/Screenshot_2.png new file mode 100644 index 0000000..abba1a1 Binary files /dev/null and b/Screenshot_2.png differ diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..cf5cac3 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.jstappdev.e6bflightcomputer" + compileSdk = 35 + + defaultConfig { + applicationId = "com.jstappdev.e6bflightcomputer" + minSdk = 26 + targetSdk = 35 + versionCode = 1 + versionName = "1.0" + } + + buildTypes { + release { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildToolsVersion = "34.0.0" +} + +dependencies { + implementation(libs.androidx.activity.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.ui.graphics.android) +} \ 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/release/app-release.apk b/app/release/app-release.apk new file mode 100644 index 0000000..d440bb7 Binary files /dev/null and b/app/release/app-release.apk differ diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm new file mode 100644 index 0000000..260833d Binary files /dev/null and b/app/release/baselineProfiles/0/app-release.dm differ diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm new file mode 100644 index 0000000..04b0396 Binary files /dev/null and b/app/release/baselineProfiles/1/app-release.dm differ diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json new file mode 100644 index 0000000..f979018 --- /dev/null +++ b/app/release/output-metadata.json @@ -0,0 +1,37 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.jstappdev.e6bflightcomputer", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "0.1", + "outputFile": "app-release.apk" + } + ], + "elementType": "File", + "baselineProfiles": [ + { + "minApi": 28, + "maxApi": 30, + "baselineProfiles": [ + "baselineProfiles/1/app-release.dm" + ] + }, + { + "minApi": 31, + "maxApi": 2147483647, + "baselineProfiles": [ + "baselineProfiles/0/app-release.dm" + ] + } + ], + "minSdkVersionForDexing": 26 +} \ 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..608d40b --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..846b2f6 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/jstappdev/e6bflightcomputer/Back.kt b/app/src/main/java/com/jstappdev/e6bflightcomputer/Back.kt new file mode 100644 index 0000000..df4f5f2 --- /dev/null +++ b/app/src/main/java/com/jstappdev/e6bflightcomputer/Back.kt @@ -0,0 +1,28 @@ +package com.jstappdev.e6bflightcomputer + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.SwitchCompat + +class Back : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.back) + val lockButton: AppCompatImageButton = findViewById(R.id.lockButton) + val backView: BackView = findViewById(R.id.wind) + + backView.setLockButton(lockButton) + + findViewById(R.id.switch2).setOnCheckedChangeListener { _, _ -> + startActivity( + Intent( + this, Front::class.java + ) + ) + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jstappdev/e6bflightcomputer/BackView.kt b/app/src/main/java/com/jstappdev/e6bflightcomputer/BackView.kt new file mode 100644 index 0000000..94f6602 --- /dev/null +++ b/app/src/main/java/com/jstappdev/e6bflightcomputer/BackView.kt @@ -0,0 +1,367 @@ +package com.jstappdev.e6bflightcomputer + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.View +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import kotlin.math.atan2 +import kotlin.math.pow +import kotlin.math.sqrt + + +class BackView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private var sliderMatrixWithTranslation: Matrix = Matrix() + private var screenHeight: Int = 0 + private var screenWidth: Int = 0 + private var isDotSet: Boolean = false + private var isRotating = false + private var initialScaleFactor = 1.0f + private var sliderY = 0f + private var initialX = 0f + private var initialY = 0f + private val matrix = Matrix() + private var sliderDrawable: Drawable? = null + private var outerDrawable: Drawable? = null + private var innerDrawable: Drawable? = null + private val innerMatrix = Matrix() + private val gestureDetector = GestureDetector(context, GestureListener()) + private var scaleGestureDetector = ScaleGestureDetector(context, ScaleListener()) + private var scaleFactor = 1f + private var isZoomedIn = false + private var currentRotation = 0f + private var lastAngle = 0f + private var isPanning = false + private var lastTouchX = 0f + private var lastTouchY = 0f + private val paint = Paint() + private var dotX = 0f + private var dotY = 0f + private lateinit var lockButton: AppCompatImageButton + private var lockState: LockState = LockState.UNLOCKED + + enum class LockState { + UNLOCKED, PARTIALLY_LOCKED, FULLY_LOCKED + } + + fun setLockButton(button: AppCompatImageButton) { + this.lockButton = button + lockButton.setBackgroundResource(android.R.drawable.ic_menu_rotate) + + lockButton.setOnClickListener { v: View? -> + lockState = when (lockState) { + LockState.UNLOCKED -> LockState.PARTIALLY_LOCKED + LockState.PARTIALLY_LOCKED -> LockState.FULLY_LOCKED + LockState.FULLY_LOCKED -> LockState.UNLOCKED + } + updateLockButton(lockButton) + } + } + + private fun updateLockButton(v: AppCompatImageButton) { + when (lockState) { + LockState.UNLOCKED -> { + v.setImageDrawable( + ContextCompat.getDrawable( + context, android.R.drawable.ic_notification_overlay + ) + ) + v.setBackgroundResource( + android.R.drawable.ic_menu_rotate + ) + } + + LockState.PARTIALLY_LOCKED -> { + v.setBackgroundResource(android.R.drawable.ic_menu_rotate) + v.setImageDrawable(null) + } + + LockState.FULLY_LOCKED -> { + v.setBackgroundResource(android.R.drawable.ic_lock_lock) + v.setImageDrawable(null) + } + } + } + + init { + sliderDrawable = ContextCompat.getDrawable(context, R.drawable.wind_slider) + sliderDrawable?.setBounds( + 0, 0, sliderDrawable!!.intrinsicWidth, sliderDrawable!!.intrinsicHeight + ) + outerDrawable = ContextCompat.getDrawable(context, R.drawable.wind_outer) + outerDrawable?.setBounds( + 0, 0, outerDrawable!!.intrinsicWidth, outerDrawable!!.intrinsicHeight + ) + innerDrawable = ContextCompat.getDrawable(context, R.drawable.wind_dial) + innerDrawable?.setBounds( + 0, 0, innerDrawable!!.intrinsicWidth, innerDrawable!!.intrinsicHeight + ) + paint.color = Color.RED + paint.style = Paint.Style.FILL + } + + private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + // Adjust the scale factor based on the scale gesture + scaleFactor *= detector.scaleFactor + + // Restrict the scale factor to a certain range to prevent excessive zooming + scaleFactor = scaleFactor.coerceIn(0.1f, 5.0f) + + // Apply the scale transformation + matrix.postScale( + detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY + ) + + // Update the slider matrix to match the new transformation + invalidate() + + return true + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + screenWidth = MeasureSpec.getSize(widthMeasureSpec) + screenHeight = MeasureSpec.getSize(heightMeasureSpec) + initialScaleFactor = screenWidth.toFloat() / outerDrawable!!.intrinsicWidth.toFloat() + scaleFactor = initialScaleFactor + + matrix.reset() + + val drawableWidth = outerDrawable?.intrinsicWidth?.times(scaleFactor) ?: 0f + val drawableHeight = outerDrawable?.intrinsicHeight?.times(scaleFactor) ?: 0f + + val dx = (screenWidth - drawableWidth) / 2f + val dy = (screenHeight - drawableHeight) / 2f + + initialX = dx + initialY = dy + + matrix.postScale(scaleFactor, scaleFactor) + matrix.postTranslate(dx, dy) + + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + // Draw the sliderDrawable with a scaled vertical translation of sliderY + canvas.save() + sliderMatrixWithTranslation.set(matrix) + sliderMatrixWithTranslation.postTranslate( + 0f, sliderY * scaleFactor + ) // Scale the translation + canvas.concat(sliderMatrixWithTranslation) + sliderDrawable?.draw(canvas) + canvas.restore() + + // Draw the outerDrawable without any additional transformation + canvas.save() + canvas.concat(matrix) + outerDrawable?.draw(canvas) + + innerMatrix.reset() + innerMatrix.postRotate( + currentRotation, + (outerDrawable?.intrinsicWidth ?: 0) / 2f, + (outerDrawable?.intrinsicHeight ?: 0) / 2f + ) + canvas.concat(innerMatrix) + innerDrawable?.draw(canvas) + + // Draw the wind mark (dot), if set, on top of everything + if (isDotSet) { + canvas.drawCircle(dotX, dotY, 10f, paint) + } + + canvas.restore() + } + + + private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(event: MotionEvent): Boolean { + + scaleFactor = if (isZoomedIn) { + initialScaleFactor + } else { + 1.5f + } + + val drawableWidth = outerDrawable?.intrinsicWidth?.times(scaleFactor) ?: 0f + val drawableHeight = outerDrawable?.intrinsicHeight?.times(scaleFactor) ?: 0f + + val dx = (screenWidth - drawableWidth) / 2f + val dy = (screenHeight - drawableHeight) / 2f + + matrix.reset() + matrix.postScale(scaleFactor, scaleFactor) + matrix.postTranslate(dx, dy + 590f.times(scaleFactor)) + + isZoomedIn = !isZoomedIn + + invalidate() + + return true + } + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + gestureDetector.onTouchEvent(event) + scaleGestureDetector.onTouchEvent(event) + + when (event.action and MotionEvent.ACTION_MASK) { + MotionEvent.ACTION_DOWN -> { + if (lockState != LockState.FULLY_LOCKED && isTouchInsideDial(event.x, event.y)) { + lastAngle = calculateAngle(event.x, event.y) + isRotating = true + } else { + isPanning = true + isRotating = false + lastTouchX = event.x + lastTouchY = event.y + } + } + + MotionEvent.ACTION_MOVE -> { + if (isRotating) { + val newAngle = calculateAngle(event.x, event.y) + val deltaAngle = newAngle - lastAngle + currentRotation += deltaAngle + lastAngle = newAngle + invalidate() + } else if (isPanning) { + if (event.pointerCount == 1) { + val dx = event.x - lastTouchX + val dy = event.y - lastTouchY + if (lockState != LockState.FULLY_LOCKED && isTouchOutsideOuterDial( + event.x, event.y + ) + ) sliderY += dy + else matrix.postTranslate(dx, dy) + + invalidate() + + lastTouchX = event.x + lastTouchY = event.y + } + } + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + isRotating = false + isPanning = false + } + } + + return true + } + + private fun isTouchInsideDial(x: Float, y: Float): Boolean { + // Center of the drawable after translation and scaling + val centerX = outerDrawable!!.bounds.centerX().toFloat() + val centerY = outerDrawable!!.bounds.centerY().toFloat() + + // Calculate the radius of the circle (considering scaling) + val radius = (innerDrawable?.intrinsicWidth ?: 0) / 2f * scaleFactor + + // Apply the inverse of the outer matrix (translation + scaling) + val inverseOuterMatrix = Matrix() + matrix.invert(inverseOuterMatrix) + + val point = floatArrayOf(x, y) + inverseOuterMatrix.mapPoints(point) + + // Now apply the inverse of the rotation around the center + val angleInRadians = Math.toRadians(-currentRotation.toDouble()).toFloat() + val sinAngle = Math.sin(angleInRadians.toDouble()).toFloat() + val cosAngle = Math.cos(angleInRadians.toDouble()).toFloat() + + val dx = point[0] - centerX + val dy = point[1] - centerY + + // Apply reverse rotation + val transformedX = centerX + (dx * cosAngle - dy * sinAngle) + val transformedY = centerY + (dx * sinAngle + dy * cosAngle) + + // Calculate the distance from the transformed touch point to the center of the circle + val distance = + sqrt((transformedX - centerX).pow(2) + (transformedY - centerY).pow(2)) * scaleFactor + + // Optionally, update the dot position if the touch is inside transparent area + val isInnerDial = distance <= radius - 170 * scaleFactor + if (lockState == LockState.UNLOCKED && isInnerDial && !isPanning && !isRotating) { + dotX = transformedX + dotY = transformedY + isDotSet = true + invalidate() + } + + return distance <= radius && !isInnerDial + } + + + private fun isTouchOutsideOuterDial(x: Float, y: Float): Boolean { + val centerX = outerDrawable!!.bounds.centerX() + val centerY = outerDrawable!!.bounds.centerY() + + // Calculate the radius of the circle + val radius = (innerDrawable?.intrinsicWidth ?: 0) / 2f + // Apply the inverse matrix transformation to get the position before transformation + val inverseMatrix = Matrix() + matrix.invert(inverseMatrix) + + val point = floatArrayOf(x, y) + inverseMatrix.mapPoints(point) + + // Get the transformed touch coordinates + val transformedX = point[0] + val transformedY = point[1] + + // Calculate the distance from the touch point to the center of the circle + val distance = sqrt(((transformedX - centerX).pow(2) + (transformedY - centerY).pow(2))) + + // Return true if the distance is less than or equal to the radius + return distance > (radius + 120) + } + + + private fun calculateAngle(x: Float, y: Float): Float { + // Get the center of the outerDrawable in screen coordinates + val centerX = outerDrawable!!.bounds.centerX() + val centerY = outerDrawable!!.bounds.centerY() + + // Apply the inverse matrix transformation to get the position before transformation + val inverseMatrix = Matrix() + matrix.invert(inverseMatrix) + + val point = floatArrayOf(x, y) + inverseMatrix.mapPoints(point) + + // Get the transformed touch coordinates + val dx = point[0] + val dy = point[1] + + // Calculate the angle from the adjusted touch point to the center of the drawable + val angle = Math.toDegrees( + atan2((dy - centerY).toDouble(), (dx - centerX).toDouble()) + ).toFloat() + + // Normalize the angle to be between 0 and 360 degrees + return if (angle < 0) angle + 360f else angle + } +} diff --git a/app/src/main/java/com/jstappdev/e6bflightcomputer/Front.kt b/app/src/main/java/com/jstappdev/e6bflightcomputer/Front.kt new file mode 100644 index 0000000..4117464 --- /dev/null +++ b/app/src/main/java/com/jstappdev/e6bflightcomputer/Front.kt @@ -0,0 +1,27 @@ +package com.jstappdev.e6bflightcomputer + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.AppCompatImageButton +import androidx.appcompat.widget.SwitchCompat + +class Front : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.front) + val lockButton: AppCompatImageButton = findViewById(R.id.lockButton) + val frontView: FrontView = findViewById(R.id.tas) + + frontView.setLockButton(lockButton) + + findViewById(R.id.switch1).setOnCheckedChangeListener { _, _ -> + startActivity( + Intent( + this, Back::class.java + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/jstappdev/e6bflightcomputer/FrontView.kt b/app/src/main/java/com/jstappdev/e6bflightcomputer/FrontView.kt new file mode 100644 index 0000000..cc3605f --- /dev/null +++ b/app/src/main/java/com/jstappdev/e6bflightcomputer/FrontView.kt @@ -0,0 +1,247 @@ +package com.jstappdev.e6bflightcomputer + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.ScaleGestureDetector +import android.view.View +import androidx.appcompat.widget.AppCompatImageButton +import androidx.core.content.ContextCompat +import kotlin.math.atan2 +import kotlin.math.pow +import kotlin.math.sqrt + +class FrontView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + + private var isRotating = false + private var initialScaleFactor = 1.0f + private val matrix = Matrix() + private var outerDrawable: Drawable? = null + private var innerDrawable: Drawable? = null + private val innerMatrix = Matrix() + private val gestureDetector = GestureDetector(context, GestureListener()) + private var scaleFactor = 1f + private var isZoomedIn = false + private var currentRotation = 290.5f + private var lastAngle = 0f + private var isPanning = false + private var lastTouchX = 0f + private var lastTouchY = 0f + private lateinit var lockButton: AppCompatImageButton + private var isLocked = false + private var scaleGestureDetector: ScaleGestureDetector + + init { + outerDrawable = ContextCompat.getDrawable(context, R.drawable.tasbase) + outerDrawable?.setBounds( + 0, 0, outerDrawable!!.intrinsicWidth, outerDrawable!!.intrinsicHeight + ) + innerDrawable = ContextCompat.getDrawable(context, R.drawable.tasdial) + innerDrawable?.setBounds( + 0, 0, innerDrawable!!.intrinsicWidth, innerDrawable!!.intrinsicHeight + ) + scaleGestureDetector = ScaleGestureDetector(context, ScaleListener()) + } + + private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { + override fun onScale(detector: ScaleGestureDetector): Boolean { + // Adjust the scale factor based on the scale gesture + scaleFactor *= detector.scaleFactor + + // Restrict the scale factor to a certain range to prevent excessive zooming + scaleFactor = scaleFactor.coerceIn(0.1f, 5.0f) + + // Apply the scale transformation + matrix.postScale( + detector.scaleFactor, detector.scaleFactor, detector.focusX, detector.focusY + ) + + // Update the slider matrix to match the new transformation + invalidate() + + return true + } + } + + fun setLockButton(button: AppCompatImageButton) { + lockButton = button + lockButton.setBackgroundResource(android.R.drawable.ic_menu_rotate) + + lockButton.setOnClickListener { v: View? -> + isLocked = !isLocked + if (isLocked) { + v!!.setBackgroundResource(android.R.drawable.ic_lock_lock) + } else { + v!!.setBackgroundResource( + android.R.drawable.ic_menu_rotate + ) + } + } + } + + private fun centerDrawable() { + matrix.reset() + + val drawableWidth = outerDrawable?.intrinsicWidth?.times(scaleFactor) ?: 0f + val drawableHeight = outerDrawable?.intrinsicHeight?.times(scaleFactor) ?: 0f + + val dx = (width - drawableWidth) / 2f + val dy = (height - drawableHeight) / 2f + + matrix.postScale(scaleFactor, scaleFactor) + matrix.postTranslate(dx, dy) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val w = MeasureSpec.getSize(widthMeasureSpec) + val h = MeasureSpec.getSize(heightMeasureSpec) + initialScaleFactor = w.toFloat() / outerDrawable!!.intrinsicWidth.toFloat() + scaleFactor = initialScaleFactor + + matrix.reset() + + val drawableWidth = outerDrawable?.intrinsicWidth?.times(scaleFactor) ?: 0f + val drawableHeight = outerDrawable?.intrinsicHeight?.times(scaleFactor) ?: 0f + + val dx = (w - drawableWidth) / 2f + val dy = (h - drawableHeight) / 2f + + matrix.postScale(scaleFactor, scaleFactor) + matrix.postTranslate(dx, dy) + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.save() + canvas.concat(matrix) + outerDrawable?.draw(canvas) + canvas.save() + + innerMatrix.reset() + innerMatrix.postRotate( + currentRotation, + outerDrawable!!.intrinsicWidth / 2f, + outerDrawable!!.intrinsicHeight / 2f + ) + canvas.concat(innerMatrix) + innerDrawable?.draw(canvas) + + canvas.restore() + } + + private inner class GestureListener : GestureDetector.SimpleOnGestureListener() { + override fun onDoubleTap(event: MotionEvent): Boolean { + isZoomedIn = !isZoomedIn + scaleFactor = if (isZoomedIn) { + 1.1f + } else { + initialScaleFactor + } + centerDrawable() + invalidate() + return true + } + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + gestureDetector.onTouchEvent(event) + scaleGestureDetector.onTouchEvent(event) + + when (event.action and MotionEvent.ACTION_MASK) { + MotionEvent.ACTION_DOWN -> { + if (!isLocked && isTouchInsideCircle(event.x, event.y)) { + lastAngle = calculateAngle(event.x, event.y) + isRotating = true + } else { + isPanning = true + isRotating = false + lastTouchX = event.x + lastTouchY = event.y + } + } + + MotionEvent.ACTION_MOVE -> { + if (isRotating) { + val newAngle = calculateAngle(event.x, event.y) + val deltaAngle = newAngle - lastAngle + currentRotation += deltaAngle + lastAngle = newAngle + invalidate() + } else if (isPanning) { + val dx = event.x - lastTouchX + val dy = event.y - lastTouchY + matrix.postTranslate(dx, dy) + lastTouchX = event.x + lastTouchY = event.y + invalidate() + } + } + + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + isRotating = false + isPanning = false + } + } + + return true + } + + private fun isTouchInsideCircle(x: Float, y: Float): Boolean { + val centerX = outerDrawable!!.bounds.centerX() + val centerY = outerDrawable!!.bounds.centerY() + + // Calculate the radius of the circle + val radius = (innerDrawable?.intrinsicWidth ?: 0) / 2f - 110 + // Apply the inverse matrix transformation to get the position before transformation + val inverseMatrix = Matrix() + matrix.invert(inverseMatrix) + + val point = floatArrayOf(x, y) + inverseMatrix.mapPoints(point) + + // Get the transformed touch coordinates + val transformedX = point[0] + val transformedY = point[1] + + // Calculate the distance from the touch point to the center of the circle + val distance = sqrt(((transformedX - centerX).pow(2) + (transformedY - centerY).pow(2))) + + // Return true if the distance is less than or equal to the radius + return distance <= radius + } + + private fun calculateAngle(x: Float, y: Float): Float { + // Get the center of the outerDrawable in screen coordinates + val centerX = outerDrawable!!.bounds.centerX() + val centerY = outerDrawable!!.bounds.centerY() + + // Apply the inverse matrix transformation to get the position before transformation + val inverseMatrix = Matrix() + matrix.invert(inverseMatrix) + + val point = floatArrayOf(x, y) + inverseMatrix.mapPoints(point) + + // Get the transformed touch coordinates + val dx = point[0] + val dy = point[1] + + // Calculate the angle from the adjusted touch point to the center of the drawable + val angle = Math.toDegrees( + atan2((dy - centerY).toDouble(), (dx - centerX).toDouble()) + ).toFloat() + + // Normalize the angle to be between 0 and 360 degrees + return if (angle < 0) angle + 360f else angle + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..725b21d --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..a83cd23 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tasbase.xml b/app/src/main/res/drawable/tasbase.xml new file mode 100644 index 0000000..6eedc7e --- /dev/null +++ b/app/src/main/res/drawable/tasbase.xml @@ -0,0 +1,3768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/tasdial.xml b/app/src/main/res/drawable/tasdial.xml new file mode 100644 index 0000000..9b961a2 --- /dev/null +++ b/app/src/main/res/drawable/tasdial.xml @@ -0,0 +1,3660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/wind_dial.xml b/app/src/main/res/drawable/wind_dial.xml new file mode 100644 index 0000000..084ee19 --- /dev/null +++ b/app/src/main/res/drawable/wind_dial.xml @@ -0,0 +1,2177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/wind_outer.xml b/app/src/main/res/drawable/wind_outer.xml new file mode 100644 index 0000000..1c4008a --- /dev/null +++ b/app/src/main/res/drawable/wind_outer.xml @@ -0,0 +1,1083 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/wind_slider.xml b/app/src/main/res/drawable/wind_slider.xml new file mode 100644 index 0000000..b12bb17 --- /dev/null +++ b/app/src/main/res/drawable/wind_slider.xml @@ -0,0 +1,2625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/back.xml b/app/src/main/res/layout/back.xml new file mode 100644 index 0000000..8a0f0da --- /dev/null +++ b/app/src/main/res/layout/back.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/front.xml b/app/src/main/res/layout/front.xml new file mode 100644 index 0000000..29d1771 --- /dev/null +++ b/app/src/main/res/layout/front.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..a83cd23 --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..48f8add Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..cb71ee0 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..513670b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..513670b Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..6dd82fb Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..0b044ae Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..07ac060 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..94930c3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..94930c3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..396ff64 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..95b11c8 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..9d7e4b6 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..0539d28 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..0539d28 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d3875d5 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..2ed7e82 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..8c5adc7 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4d91461 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..4d91461 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..440ac0b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..79ada2d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..b87068d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..ca9b2bf Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png new file mode 100644 index 0000000..ca9b2bf Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_monochrome.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bbf96e5 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..7e0be1f --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + E6B Flight Computer + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..278c1f2 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +