-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: AkaneTan <dev@akane.uk>
- Loading branch information
Showing
65 changed files
with
2,599 additions
and
196 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
app/src/main/java/uk/akane/omni/logic/services/CompassTileService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
package uk.akane.omni.logic.services | ||
|
||
import android.annotation.SuppressLint | ||
import android.app.NotificationChannel | ||
import android.app.NotificationManager | ||
import android.graphics.Bitmap | ||
import android.graphics.Canvas | ||
import android.graphics.Color | ||
import android.graphics.PorterDuff | ||
import android.graphics.drawable.Drawable | ||
import android.graphics.drawable.Icon | ||
import android.hardware.Sensor | ||
import android.hardware.SensorEvent | ||
import android.hardware.SensorEventListener | ||
import android.hardware.SensorManager | ||
import android.service.quicksettings.Tile | ||
import android.service.quicksettings.TileService | ||
import android.util.Log | ||
import android.view.Surface | ||
import androidx.appcompat.content.res.AppCompatResources | ||
import androidx.core.app.NotificationCompat | ||
import androidx.core.content.ContextCompat | ||
import androidx.core.content.getSystemService | ||
import uk.akane.omni.R | ||
import kotlin.math.absoluteValue | ||
|
||
class CompassTileService : TileService(), SensorEventListener { | ||
|
||
private val sensorManager | ||
get() = getSystemService<SensorManager>() | ||
private val rotationVectorSensor | ||
get() = sensorManager?.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR) | ||
private val notificationManager | ||
get() = getSystemService<NotificationManager>() | ||
private lateinit var rotationIcon: Drawable | ||
private lateinit var iconBitmap: Bitmap | ||
|
||
|
||
companion object { | ||
const val CHANNEL_ID = "COMPASS_CHANNEL" | ||
const val NOTIFICATION_ID = 1 | ||
} | ||
|
||
override fun onCreate() { | ||
super.onCreate() | ||
notificationManager?.createNotificationChannel( | ||
NotificationChannel( | ||
CHANNEL_ID, | ||
getString(R.string.compass_tile_notification_channel), | ||
NotificationManager.IMPORTANCE_LOW | ||
) | ||
) | ||
rotationIcon = AppCompatResources.getDrawable(this, R.drawable.ic_pointer)!! | ||
iconBitmap = Bitmap.createBitmap( | ||
rotationIcon.intrinsicWidth, rotationIcon.intrinsicHeight, | ||
Bitmap.Config.ARGB_8888 | ||
) | ||
} | ||
|
||
override fun onClick() { | ||
super.onClick() | ||
Log.d("TAG", "onClick") | ||
qsTile.state = when (qsTile.state) { | ||
Tile.STATE_ACTIVE -> Tile.STATE_INACTIVE | ||
Tile.STATE_INACTIVE -> Tile.STATE_ACTIVE | ||
else -> Tile.STATE_INACTIVE | ||
} | ||
if (qsTile.state == Tile.STATE_INACTIVE) { | ||
qsTile.label = getString(R.string.compass) | ||
} | ||
qsTile.updateTile() | ||
} | ||
|
||
override fun onStartListening() { | ||
super.onStartListening() | ||
Log.d("TAG", "START LISTENING") | ||
if (qsTile.state == Tile.STATE_ACTIVE) { | ||
startForeground( | ||
NOTIFICATION_ID, | ||
NotificationCompat.Builder(this, CHANNEL_ID).setSmallIcon(R.drawable.ic_explorer) | ||
.setContentTitle(getString(R.string.compass_notification_title)) | ||
.setContentText(getString(R.string.compass_notification_label)) | ||
.build() | ||
) | ||
sensorManager?.registerListener( | ||
this, | ||
rotationVectorSensor, | ||
SensorManager.SENSOR_DELAY_FASTEST | ||
) | ||
} | ||
} | ||
|
||
override fun onStopListening() { | ||
super.onStopListening() | ||
if (qsTile.state == Tile.STATE_ACTIVE) { | ||
sensorManager?.unregisterListener(this) | ||
stopForeground(STOP_FOREGROUND_REMOVE) | ||
} | ||
Log.d("TAG", "STOP LISTENING") | ||
} | ||
|
||
override fun onSensorChanged(event: SensorEvent) { | ||
if (event.sensor.type == Sensor.TYPE_ROTATION_VECTOR && qsTile.state == Tile.STATE_ACTIVE) { | ||
updateCompass(event) | ||
} | ||
} | ||
|
||
@SuppressLint("StringFormatMatches") | ||
private fun updateCompass(event: SensorEvent) { | ||
val rotationVector = event.values.take(3).toFloatArray() | ||
val rotationMatrix = FloatArray(9) | ||
SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVector) | ||
|
||
val displayRotation = ContextCompat.getDisplayOrDefault(baseContext).rotation | ||
val remappedRotationMatrix = remapRotationMatrix(rotationMatrix, displayRotation) | ||
|
||
val orientationInRadians = FloatArray(3) | ||
SensorManager.getOrientation(remappedRotationMatrix, orientationInRadians) | ||
|
||
val azimuthInDegrees = Math.toDegrees(orientationInRadians[0].toDouble()).toFloat() | ||
val adjustedAzimuth = (azimuthInDegrees + 360) % 360 | ||
|
||
Canvas(iconBitmap).apply { | ||
drawColor(Color.BLACK, PorterDuff.Mode.CLEAR) // clear all | ||
rotate(-adjustedAzimuth, width / 2f, height / 2f) | ||
rotationIcon.setBounds(0, 0, width, height) | ||
rotationIcon.draw(this) | ||
} | ||
|
||
qsTile.label = getString(R.string.degree_format_tile, adjustedAzimuth.toInt().absoluteValue) | ||
qsTile.icon = Icon.createWithBitmap(iconBitmap) | ||
|
||
qsTile.updateTile() | ||
} | ||
|
||
private fun remapRotationMatrix(rotationMatrix: FloatArray, displayRotation: Int): FloatArray { | ||
val (newX, newY) = when (displayRotation) { | ||
Surface.ROTATION_90 -> Pair(SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X) | ||
Surface.ROTATION_180 -> Pair(SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y) | ||
Surface.ROTATION_270 -> Pair(SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X) | ||
else -> Pair(SensorManager.AXIS_X, SensorManager.AXIS_Y) | ||
} | ||
|
||
val remappedRotationMatrix = FloatArray(9) | ||
SensorManager.remapCoordinateSystem(rotationMatrix, newX, newY, remappedRotationMatrix) | ||
return remappedRotationMatrix | ||
} | ||
|
||
override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) = Unit | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
85 changes: 85 additions & 0 deletions
85
app/src/main/java/uk/akane/omni/ui/components/RulerView.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package uk.akane.omni.ui.components | ||
|
||
import android.content.Context | ||
import android.graphics.Canvas | ||
import android.graphics.Paint | ||
import android.util.AttributeSet | ||
import android.util.TypedValue | ||
import android.view.View | ||
import uk.akane.omni.logic.dpToPx | ||
import com.google.android.material.color.MaterialColors | ||
import uk.akane.omni.R | ||
|
||
class RulerView @JvmOverloads constructor( | ||
context: Context, | ||
attrs: AttributeSet? = null, | ||
defStyleAttr: Int = 0 | ||
) : View(context, attrs, defStyleAttr) { | ||
|
||
private val paintText = Paint().apply { | ||
color = MaterialColors.getColor(this@RulerView, com.google.android.material.R.attr.colorOutline) | ||
strokeWidth = 2f.dpToPx(context) | ||
textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 20f, resources.displayMetrics) | ||
typeface = resources.getFont(R.font.hgm) | ||
isAntiAlias = true | ||
} | ||
|
||
private val paintMain = Paint().apply { | ||
color = MaterialColors.getColor(this@RulerView, com.google.android.material.R.attr.colorOutline) | ||
strokeWidth = 2f.dpToPx(context) | ||
isAntiAlias = true | ||
} | ||
|
||
private val paintSide = Paint().apply { | ||
color = MaterialColors.getColor(this@RulerView, com.google.android.material.R.attr.colorOutline) | ||
alpha = 127 | ||
strokeWidth = 2f.dpToPx(context) | ||
isAntiAlias = true | ||
} | ||
|
||
// Calculate 1mm in pixels | ||
private val mmToPx: Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_MM, 1f, resources.displayMetrics) | ||
private val topPadding: Float = 24f.dpToPx(context) | ||
|
||
override fun onDraw(canvas: Canvas) { | ||
super.onDraw(canvas) | ||
|
||
val width = width.toFloat() | ||
val height = height.toFloat() | ||
|
||
val numDivisions = ((height - topPadding) / mmToPx).toInt() | ||
val longLineLength = width * 0.53f | ||
val midLineLength = width * 0.43f | ||
val shortLineLength = width * 0.34f | ||
|
||
for (i in 0..numDivisions) { | ||
val y = topPadding + i * mmToPx | ||
when { | ||
i % 10 == 0 -> { | ||
// Draw longer lines and numbers for every 10mm (1cm) | ||
canvas.drawLine(width - longLineLength, y, width, y, paintMain) | ||
val text = (i / 10).toString() | ||
val textWidth = paintText.measureText(text) | ||
val textHeight = paintText.descent() - paintText.ascent() | ||
val textX = (width - longLineLength) / 2 - textWidth / 2 | ||
val textY = y + textHeight / 3 | ||
paintText.color = MaterialColors.getColor(this@RulerView, | ||
if ((i / 10) % 5 == 0) | ||
com.google.android.material.R.attr.colorOnSurface | ||
else | ||
com.google.android.material.R.attr.colorOutline | ||
) | ||
canvas.drawText(text, textX, textY, paintText) | ||
} | ||
i % 5 == 0 -> { | ||
// Draw medium lines for every 5mm (0.5cm) | ||
canvas.drawLine(width - midLineLength, y, width, y, paintSide) | ||
} | ||
else -> { | ||
// Draw shorter lines for other millimeters | ||
canvas.drawLine(width - shortLineLength, y, width, y, paintSide) | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.