Aperture: Add an info chip
* As of now it only shows low battery warning and video settings
Change-Id: I4a0d10b13a6a4de3cb3e6392179cbf38b137ca14
diff --git a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
index b285e68..1b22884 100644
--- a/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
+++ b/app/src/main/java/org/lineageos/aperture/CameraActivity.kt
@@ -8,7 +8,10 @@
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.app.KeyguardManager
+import android.content.BroadcastReceiver
+import android.content.Context
import android.content.Intent
+import android.content.IntentFilter
import android.content.pm.ActivityInfo
import android.graphics.BitmapFactory
import android.graphics.Color
@@ -101,6 +104,7 @@
import org.lineageos.aperture.ui.CountDownView
import org.lineageos.aperture.ui.GridView
import org.lineageos.aperture.ui.HorizontalSlider
+import org.lineageos.aperture.ui.InfoChipView
import org.lineageos.aperture.ui.LensSelectorLayout
import org.lineageos.aperture.ui.LevelerView
import org.lineageos.aperture.ui.LocationPermissionsDialog
@@ -147,6 +151,7 @@
private val googleLensButton by lazy { findViewById<ImageButton>(R.id.googleLensButton) }
private val gridButton by lazy { findViewById<Button>(R.id.gridButton) }
private val gridView by lazy { findViewById<GridView>(R.id.gridView) }
+ private val infoChipView by lazy { findViewById<InfoChipView>(R.id.infoChipView) }
private val lensSelectorLayout by lazy { findViewById<LensSelectorLayout>(R.id.lensSelectorLayout) }
private val levelerView by lazy { findViewById<LevelerView>(R.id.levelerView) }
private val micButton by lazy { findViewById<Button>(R.id.micButton) }
@@ -484,6 +489,14 @@
}
}
+ private val batteryBroadcastReceiver by lazy {
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ infoChipView.batteryIntent = intent
+ }
+ }
+ }
+
enum class ShutterAnimation(val resourceId: Int) {
InitPhoto(R.drawable.avd_photo_capture),
InitVideo(R.drawable.avd_mode_video_photo),
@@ -577,6 +590,7 @@
// Pass the view model to the views
capturePreviewLayout.cameraViewModel = model
countDownView.cameraViewModel = model
+ infoChipView.cameraViewModel = model
// Restore settings from shared preferences
gridMode = sharedPreferences.lastGridMode
@@ -1153,6 +1167,9 @@
powerManager.addThermalStatusListener(onThermalStatusChangedListener)
}
+ // Start observing battery status
+ registerReceiver(batteryBroadcastReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
+
// Re-bind the use cases
bindCameraUseCases()
}
@@ -1169,6 +1186,9 @@
powerManager.removeThermalStatusListener(onThermalStatusChangedListener)
}
+ // Remove battery status receiver
+ unregisterReceiver(batteryBroadcastReceiver)
+
super.onPause()
}
diff --git a/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt b/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt
new file mode 100644
index 0000000..0fcaa22
--- /dev/null
+++ b/app/src/main/java/org/lineageos/aperture/ui/InfoChipView.kt
@@ -0,0 +1,175 @@
+/*
+ * SPDX-FileCopyrightText: 2023 The LineageOS Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.lineageos.aperture.ui
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.content.Context
+import android.content.Intent
+import android.os.BatteryManager
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.isVisible
+import androidx.core.view.updateLayoutParams
+import androidx.lifecycle.findViewTreeLifecycleOwner
+import org.lineageos.aperture.R
+import org.lineageos.aperture.camera.CameraMode
+import org.lineageos.aperture.camera.CameraViewModel
+import org.lineageos.aperture.utils.Rotation
+import kotlin.math.roundToInt
+
+class InfoChipView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : ConstraintLayout(context, attrs, defStyleAttr) {
+ // Views
+ private val lowBatteryImageView by lazy { findViewById<ImageView>(R.id.lowBatteryImageView) }
+ private val videoMicMutedImageView by lazy { findViewById<ImageView>(R.id.videoMicMutedImageView) }
+
+ // System services
+ private val layoutInflater = context.getSystemService(LayoutInflater::class.java)
+
+ private val shortAnimationDuration by lazy {
+ resources.getInteger(android.R.integer.config_shortAnimTime).toLong()
+ }
+
+ internal var batteryIntent: Intent? = null
+ set(value) {
+ field = value
+
+ update()
+ }
+ internal var cameraViewModel: CameraViewModel? = null
+ set(value) {
+ val lifecycleOwner = findViewTreeLifecycleOwner() ?: return
+
+ field?.let {
+ // Unregister
+ it.screenRotation.removeObservers(lifecycleOwner)
+ it.cameraMode.removeObservers(lifecycleOwner)
+ it.videoMicMode.removeObservers(lifecycleOwner)
+ }
+
+ field = value
+
+ value?.let { cameraViewModel ->
+ cameraViewModel.screenRotation.observe(lifecycleOwner) {
+ updateRotation()
+ }
+ cameraViewModel.cameraMode.observe(lifecycleOwner) {
+ update()
+ }
+ cameraViewModel.videoMicMode.observe(lifecycleOwner) {
+ update()
+ }
+ }
+ }
+
+ init {
+ layoutInflater.inflate(R.layout.info_chip_view, this)
+ }
+
+ private fun update() {
+ val cameraViewModel = cameraViewModel ?: return
+
+ val cameraMode = cameraViewModel.cameraMode.value ?: return
+ val videoMicMode = cameraViewModel.videoMicMode.value ?: return
+
+ // Get the new visibility values
+ val lowBatteryImageViewVisible = batteryIntent?.let {
+ val level = it.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)
+ val scale = it.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
+
+ val batteryPercentage = ((level * 100) / scale.toFloat()).roundToInt()
+
+ batteryPercentage <= 15
+ } ?: false
+ val videoMicMutedImageViewVisible = cameraMode == CameraMode.VIDEO && !videoMicMode
+
+ val shouldBeVisible = listOf(
+ lowBatteryImageViewVisible,
+ videoMicMutedImageViewVisible,
+ ).any { it }
+
+ val postUpdate = {
+ // Set the visibility on the views
+ lowBatteryImageView.isVisible = lowBatteryImageViewVisible
+ videoMicMutedImageView.isVisible = videoMicMutedImageViewVisible
+
+ updateRotation()
+ }
+
+ if (shouldBeVisible && !isVisible) {
+ postUpdate()
+
+ // Set the content view to 0% opacity but visible, so that it is visible
+ // (but fully transparent) during the animation.
+ alpha = 0f
+ isVisible = true
+
+ // Animate the content view to 100% opacity, and clear any animation
+ // listener set on the view.
+ animate()
+ .alpha(1f)
+ .setDuration(shortAnimationDuration)
+ .setListener(null)
+ } else if (!shouldBeVisible && isVisible) {
+ animate()
+ .alpha(0f)
+ .setDuration(shortAnimationDuration)
+ .setListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ isVisible = false
+
+ postUpdate()
+ }
+ })
+ } else {
+ postUpdate()
+ }
+ }
+
+ private fun updateRotation() {
+ val cameraViewModel = cameraViewModel ?: return
+
+ val screenRotation = cameraViewModel.screenRotation.value ?: return
+
+ val compensationValue = screenRotation.compensationValue.toFloat()
+
+ updateLayoutParams<LayoutParams> {
+ startToStart = when (screenRotation) {
+ Rotation.ROTATION_0,
+ Rotation.ROTATION_90,
+ Rotation.ROTATION_180 -> R.id.viewFinder
+ Rotation.ROTATION_270 -> LayoutParams.UNSET
+ }
+ endToEnd = when (screenRotation) {
+ Rotation.ROTATION_0,
+ Rotation.ROTATION_90,
+ Rotation.ROTATION_180 -> LayoutParams.UNSET
+ Rotation.ROTATION_270 -> R.id.viewFinder
+ }
+ }
+
+ rotation = compensationValue
+
+ measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
+
+ translationX = when (screenRotation) {
+ Rotation.ROTATION_0,
+ Rotation.ROTATION_180 -> 0F
+ Rotation.ROTATION_90 -> -((measuredWidth - measuredHeight) / 2).toFloat()
+ Rotation.ROTATION_270 -> ((measuredWidth - measuredHeight) / 2).toFloat()
+ }
+ translationY = when (screenRotation) {
+ Rotation.ROTATION_0,
+ Rotation.ROTATION_180 -> 0F
+ Rotation.ROTATION_90,
+ Rotation.ROTATION_270 -> -((measuredHeight - measuredWidth) / 2).toFloat()
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_battery_1_bar.xml b/app/src/main/res/drawable/ic_battery_1_bar.xml
new file mode 100644
index 0000000..ddda245
--- /dev/null
+++ b/app/src/main/res/drawable/ic_battery_1_bar.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:tint="#FFFFFF"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M17,5v16c0,0.55 -0.45,1 -1,1H8c-0.55,0 -1,-0.45 -1,-1V5c0,-0.55 0.45,-1 1,-1h2V2h4v2h2C16.55,4 17,4.45 17,5zM15,6H9v12h6V6z" />
+</vector>
diff --git a/app/src/main/res/drawable/info_chip_view_background.xml b/app/src/main/res/drawable/info_chip_view_background.xml
new file mode 100644
index 0000000..75cfd4e
--- /dev/null
+++ b/app/src/main/res/drawable/info_chip_view_background.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ SPDX-FileCopyrightText: 2023 The LineageOS Project
+ SPDX-License-Identifier: Apache-2.0
+-->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <corners android:radius="32dp" />
+</shape>
diff --git a/app/src/main/res/layout/activity_camera.xml b/app/src/main/res/layout/activity_camera.xml
index d5f7799..414001f 100644
--- a/app/src/main/res/layout/activity_camera.xml
+++ b/app/src/main/res/layout/activity_camera.xml
@@ -64,6 +64,18 @@
app:layout_constraintStart_toStartOf="@+id/viewFinder"
app:layout_constraintTop_toTopOf="@+id/viewFinder" />
+ <org.lineageos.aperture.ui.InfoChipView
+ android:id="@+id/infoChipView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:layout_marginEnd="12dp"
+ android:layout_marginStart="12dp"
+ android:layout_marginTop="12dp"
+ android:visibility="gone"
+ app:layout_constraintStart_toStartOf="@+id/viewFinder"
+ app:layout_constraintTop_toTopOf="@+id/viewFinder" />
+
<org.lineageos.aperture.ui.CountDownView
android:id="@+id/countDownView"
android:layout_width="0dp"
diff --git a/app/src/main/res/layout/info_chip_view.xml b/app/src/main/res/layout/info_chip_view.xml
new file mode 100644
index 0000000..54a24ce
--- /dev/null
+++ b/app/src/main/res/layout/info_chip_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ SPDX-FileCopyrightText: 2023 The LineageOS Project
+ SPDX-License-Identifier: Apache-2.0
+-->
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ style="@style/Theme.Aperture.Camera.InfoChip"
+ android:layout_width="wrap_content"
+ android:layout_height="32dp"
+ android:background="@drawable/info_chip_view_background"
+ android:backgroundTint="#222222"
+ android:minWidth="32dp"
+ android:orientation="horizontal"
+ android:paddingHorizontal="12dp"
+ android:paddingVertical="8dp">
+
+ <ImageView
+ android:id="@+id/lowBatteryImageView"
+ style="@style/Theme.Aperture.Camera.InfoChip.ImageView"
+ android:src="@drawable/ic_battery_1_bar"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:tint="#EE5555" />
+
+ <ImageView
+ android:id="@+id/videoMicMutedImageView"
+ style="@style/Theme.Aperture.Camera.InfoChip.ImageView"
+ android:layout_marginStart="8dp"
+ android:src="@drawable/ic_mic_off"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/lowBatteryImageView"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginStart="0dp"
+ app:tint="@android:color/white" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 36001b9..0891a12 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -100,4 +100,13 @@
<item name="android:textSize">36sp</item>
<item name="android:drawablePadding">10dp</item>
</style>
+
+ <!-- Info chip -->
+ <style name="Theme.Aperture.Camera.InfoChip" />
+
+ <style name="Theme.Aperture.Camera.InfoChip.ImageView">
+ <item name="android:layout_width">16dp</item>
+ <item name="android:layout_height">16dp</item>
+ <item name="android:visibility">gone</item>
+ </style>
</resources>