Aperture: Implement leveler
Change-Id: If3a2c91d4b8ae43798314fd10486ad39690f16b9
diff --git a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
index 7776da2..b10238d 100644
--- a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
@@ -70,6 +70,7 @@
import com.google.android.material.slider.Slider
import org.lineageos.aperture.ui.CountDownView
import org.lineageos.aperture.ui.GridView
+import org.lineageos.aperture.ui.LevelerView
import org.lineageos.aperture.utils.CameraFacing
import org.lineageos.aperture.utils.CameraMode
import org.lineageos.aperture.utils.CameraSoundsUtils
@@ -95,6 +96,7 @@
private val galleryButton by lazy { findViewById<ImageView>(R.id.galleryButton) }
private val gridButton by lazy { findViewById<ImageButton>(R.id.gridButton) }
private val gridView by lazy { findViewById<GridView>(R.id.gridView) }
+ private val levelerView by lazy { findViewById<LevelerView>(R.id.levelerView) }
private val micButton by lazy { findViewById<ImageButton>(R.id.micButton) }
private val photoModeButton by lazy { findViewById<MaterialButton>(R.id.photoModeButton) }
private val primaryBarLayout by lazy { findViewById<ConstraintLayout>(R.id.primaryBarLayout) }
@@ -463,6 +465,9 @@
// Set bright screen
setBrightScreen(sharedPreferences.brightScreen)
+ // Set leveler
+ setLeveler(sharedPreferences.leveler)
+
// Reset tookSomething state
tookSomething = false
@@ -1186,6 +1191,10 @@
}
}
+ private fun setLeveler(enabled: Boolean) {
+ levelerView.isVisible = enabled
+ }
+
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
diff --git a/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt b/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
index 14de37b..78c4efa 100644
--- a/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
+++ b/app/src/main/java/org/lineageos/aperture/SharedPreferencesExt.kt
@@ -272,6 +272,16 @@
putBoolean(SHUTTER_SOUND_KEY, value)
}
+// Leveler
+private const val LEVELER_KEY = "leveler"
+private const val LEVELER_DEFAULT = false
+
+internal var SharedPreferences.leveler: Boolean
+ get() = getBoolean(LEVELER_KEY, LEVELER_DEFAULT)
+ set(value) = edit {
+ putBoolean(LEVELER_KEY, value)
+ }
+
// Last saved URI
private const val LAST_SAVED_URI_KEY = "saved_uri"
diff --git a/app/src/main/java/org/lineageos/aperture/ui/LevelerView.kt b/app/src/main/java/org/lineageos/aperture/ui/LevelerView.kt
new file mode 100644
index 0000000..8e13666
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/ui/LevelerView.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The LineageOS Project
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.ui
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.hardware.SensorManager
+import android.util.AttributeSet
+import android.view.OrientationEventListener
+import android.view.View
+import org.lineageos.aperture.R
+import kotlin.math.PI
+import kotlin.math.cos
+import kotlin.math.min
+import kotlin.math.roundToInt
+import kotlin.math.sin
+
+class LevelerView(context: Context, attributeSet: AttributeSet?) : View(context, attributeSet) {
+ private var currentOrientation = 0
+ private val orientationEventListener =
+ object : OrientationEventListener(context, SensorManager.SENSOR_DELAY_UI) {
+ override fun onOrientationChanged(orientation: Int) {
+ if (orientation == ORIENTATION_UNKNOWN) {
+ return
+ }
+
+ currentOrientation = orientation
+ postInvalidate()
+ }
+ }
+
+ private val defaultLevelPaint = Paint().apply {
+ isAntiAlias = true
+ strokeWidth = 4f
+ style = Paint.Style.STROKE
+ color = 0x7FFFFFFF
+ }
+
+ private val defaultBasePaint = Paint().apply {
+ isAntiAlias = true
+ strokeWidth = 4f
+ style = Paint.Style.STROKE
+ color = 0x7FFFFFFF
+ }
+
+ private val highlightPaint = Paint().apply {
+ isAntiAlias = true
+ strokeWidth = 4f
+ style = Paint.Style.STROKE
+ color = context.getColor(R.color.yellow)
+ }
+
+ override fun setVisibility(visibility: Int) {
+ super.setVisibility(visibility)
+
+ if (visibility == VISIBLE) {
+ orientationEventListener.enable()
+ } else {
+ orientationEventListener.disable()
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ super.onDraw(canvas)
+
+ val isLevel = isLevel()
+ drawBase(canvas, isLevel, isLandscape())
+
+ if (!isLevel) {
+ val radians = -((currentOrientation.toFloat() / 180F) * PI).toFloat()
+ drawLevelLine(canvas, radians)
+ }
+ }
+
+ private fun drawBase(canvas: Canvas, isLevel: Boolean, isLandscape: Boolean) {
+ val xLen = if (isLandscape) RELATIVE_BASE_LENGTH_Y else RELATIVE_BASE_LENGTH_X
+ val yLen = if (isLandscape) RELATIVE_BASE_LENGTH_X else RELATIVE_BASE_LENGTH_Y
+
+ val xLength = (min(width, height) * xLen)
+ val yLength = (min(width, height) * yLen)
+
+ val wCenter = width / 2
+ val hCenter = height / 2
+
+ val paint = if (isLevel) highlightPaint else defaultBasePaint
+
+ canvas.drawLine(
+ (wCenter - xLength).toFloat(),
+ hCenter.toFloat(),
+ (wCenter + xLength).toFloat(),
+ hCenter.toFloat(),
+ paint
+ )
+ canvas.drawLine(
+ wCenter.toFloat(),
+ (hCenter - yLength).toFloat(),
+ wCenter.toFloat(),
+ (hCenter + yLength).toFloat(),
+ paint
+ )
+ }
+
+ private fun drawLevelLine(canvas: Canvas, radians: Float) {
+ val length = (min(width, height) * RELATIVE_LINE_LENGTH).roundToInt()
+
+ val wCenter = width / 2
+ val hCenter = height / 2
+ val wOffset = cos(radians) * length
+ val hOffset = sin(radians) * length
+
+ val wStart = wCenter - wOffset
+ val hStart = hCenter - hOffset
+ val wEnd = wCenter + wOffset
+ val hEnd = hCenter + hOffset
+
+ canvas.drawLine(wStart, hStart, wEnd, hEnd, defaultLevelPaint)
+ }
+
+ private fun isLevel(): Boolean {
+ val o = currentOrientation % 90
+ return o < LEVEL_ZONE || (90 - o) < LEVEL_ZONE
+ }
+
+ private fun isLandscape(): Boolean {
+ return currentOrientation in 45..134 || currentOrientation in 225..314
+ }
+
+ companion object {
+ private const val LEVEL_ZONE = 2
+ private const val RELATIVE_LINE_LENGTH = 0.15
+ private const val RELATIVE_BASE_LENGTH_X = 0.15
+ private const val RELATIVE_BASE_LENGTH_Y = 0.01
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
index 3a8b935..7152ec7 100644
--- a/app/src/main/res/layout/activity_camera.xml
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -180,6 +180,15 @@
app:layout_constraintStart_toStartOf="@+id/viewFinder"
app:layout_constraintTop_toTopOf="@+id/viewFinder" />
+ <org.lineageos.aperture.ui.LevelerView
+ android:id="@+id/levelerView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintBottom_toBottomOf="@+id/viewFinder"
+ app:layout_constraintEnd_toEndOf="@+id/viewFinder"
+ app:layout_constraintStart_toStartOf="@+id/viewFinder"
+ app:layout_constraintTop_toTopOf="@+id/viewFinder" />
+
<org.lineageos.aperture.ui.CountDownView
android:id="@+id/countDownView"
android:layout_width="match_parent"
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index d92087d..dfddf08 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -11,5 +11,7 @@
<color name="gray_10">#FFF6FAFA</color>
<color name="gray_60">#FF2A3232</color>
<color name="dark_grey">#FF444444</color>
+ <color name="yellow">#E9B650</color>
+ <color name="blue">#4D84E9</color>
<color name="rec_red">#FFE95950</color>
</resources>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 63d87b7..7102c7a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -47,6 +47,7 @@
<!-- Preference Titles -->
<string name="general_header">General</string>
<string name="photos_header">Photos</string>
+ <string name="advanced_header">Advanced</string>
<!-- General Preferences -->
<string name="bright_screen_title">Bright screen</string>
@@ -63,6 +64,10 @@
<string name="photo_capture_mode_minimize_latency">Minimize latency</string>
<string name="photo_capture_mode_zsl">Zero shutter lag (experimental)</string>
+ <!-- Advanced Preferences -->
+ <string name="leveler_title">Leveler</string>
+ <string name="leveler_summary">Indicator that shows device orientation</string>
+
<!-- Shortcuts -->
<string name="shortcut_selfie">Take a selfie</string>
<string name="shortcut_video">Take a video</string>
diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml
index d30f49b..58ed4bd 100644
--- a/app/src/main/res/xml/root_preferences.xml
+++ b/app/src/main/res/xml/root_preferences.xml
@@ -42,4 +42,16 @@
</PreferenceCategory>
+ <PreferenceCategory
+ app:iconSpaceReserved="false"
+ app:title="@string/advanced_header">
+
+ <SwitchPreference
+ app:defaultValue="false"
+ app:iconSpaceReserved="false"
+ app:key="leveler"
+ app:summary="@string/leveler_summary"
+ app:title="@string/leveler_title" />
+ </PreferenceCategory>
+
</PreferenceScreen>