Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 3 additions & 13 deletions app/src/main/java/app/grapheneos/camera/CamConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import app.grapheneos.camera.analyzer.QRAnalyzer
import app.grapheneos.camera.ktx.markAs16by9Layout
import app.grapheneos.camera.ktx.markAs4by3Layout
import app.grapheneos.camera.ktx.applyPreviewRatio
import app.grapheneos.camera.ui.activities.CaptureActivity
import app.grapheneos.camera.ui.activities.MainActivity
import app.grapheneos.camera.ui.activities.MoreSettings
Expand Down Expand Up @@ -249,7 +248,7 @@ class CamConfig(private val mActivity: MainActivity) {
var imageCapture: ImageCapture? = null
private set

private var preview: Preview? = null
var preview: Preview? = null

val allowedFormats: ArrayList<BarcodeFormat> = arrayListOf()

Expand Down Expand Up @@ -1791,16 +1790,7 @@ class CamConfig(private val mActivity: MainActivity) {

// Focus camera on touch/tap
mActivity.previewView.setOnTouchListener(mActivity)
mActivity.previewView.apply {
when (aspectRatio) {
AspectRatio.RATIO_16_9 -> {
markAs16by9Layout()
}
AspectRatio.RATIO_4_3 -> {
markAs4by3Layout()
}
}
}
camera?.cameraInfo?.let { mActivity.previewView.applyPreviewRatio(aspectRatio, it) }

if (isInPhotoMode) {
mActivity.sensorNotifier?.forceUpdateGyro()
Expand Down
15 changes: 10 additions & 5 deletions app/src/main/java/app/grapheneos/camera/ktx/PreviewView.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
package app.grapheneos.camera.ktx

import android.view.Surface
import androidx.camera.core.AspectRatio
import androidx.camera.core.CameraInfo
import androidx.camera.view.PreviewView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams

fun PreviewView.markAs4by3Layout() = applyRatio(3.0, 4.0)
fun PreviewView.applyPreviewRatio(aspectRatio: Int, cameraInfo: CameraInfo) {
val rotation = cameraInfo.getSensorRotationDegrees(display?.rotation ?: Surface.ROTATION_0)
val (short, long) = if (aspectRatio == AspectRatio.RATIO_16_9) 9 to 16 else 3 to 4

fun PreviewView.markAs16by9Layout() = applyRatio(9.0, 16.0)

private fun PreviewView.applyRatio(width: Double, height: Double) {
updateLayoutParams<ConstraintLayout.LayoutParams> {
dimensionRatio = "H,$width:$height"
dimensionRatio = when {
rotation % 180 == 0 -> "$long:$short"
else -> "$short:$long"
}
}
}
10 changes: 0 additions & 10 deletions app/src/main/java/app/grapheneos/camera/ui/CountDownTimerUI.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import android.os.CountDownTimer
import android.util.AttributeSet
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.appcompat.widget.AppCompatTextView
import androidx.camera.core.AspectRatio
import app.grapheneos.camera.CamConfig
import app.grapheneos.camera.R
import app.grapheneos.camera.ui.activities.CaptureActivity
Expand Down Expand Up @@ -104,14 +102,6 @@ class CountDownTimerUI @JvmOverloads constructor(

private fun beforeTimeStarts() {

val params: ViewGroup.LayoutParams = layoutParams
params.height = if (camConfig.aspectRatio == AspectRatio.RATIO_4_3) {
mActivity.previewView.width * 4 / 3
} else {
mActivity.previewView.height
}
layoutParams = params

mActivity.settingsIcon.visibility = View.INVISIBLE
mActivity.thirdOption.visibility = View.INVISIBLE
mActivity.flipCameraCircle.visibility = View.INVISIBLE
Expand Down
31 changes: 12 additions & 19 deletions app/src/main/java/app/grapheneos/camera/ui/CustomGrid.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import androidx.camera.core.AspectRatio
import app.grapheneos.camera.CamConfig
import app.grapheneos.camera.ui.activities.MainActivity

Expand Down Expand Up @@ -39,22 +38,16 @@ class CustomGrid @JvmOverloads constructor(
return
}

val previewHeight = if (camConfig.aspectRatio == AspectRatio.RATIO_16_9) {
mActivity.previewView.width * 16 / 9
} else {
mActivity.previewView.width * 4 / 3
}

if (camConfig.gridType == CamConfig.GridType.GOLDEN_RATIO) {

val cx = width / 2f
val cy = previewHeight / 2f
val cy = height / 2f

val dxH = width / 8f
val dyH = previewHeight / 8f
val dyH = height / 8f

canvas.drawLine(cx - dxH, 0f, cx - dxH, previewHeight.toFloat(), paint)
canvas.drawLine(cx + dxH, 0f, cx + dxH, previewHeight.toFloat(), paint)
canvas.drawLine(cx - dxH, 0f, cx - dxH, height.toFloat(), paint)
canvas.drawLine(cx + dxH, 0f, cx + dxH, height.toFloat(), paint)
canvas.drawLine(0f, cy - dyH, width.toFloat(), cy - dyH, paint)
canvas.drawLine(0f, cy + dyH, width.toFloat(), cy + dyH, paint)

Expand All @@ -70,27 +63,27 @@ class CustomGrid @JvmOverloads constructor(
width / seed * 2f,
0f,
width / seed * 2f,
previewHeight.toFloat(),
height.toFloat(),
paint
)
canvas.drawLine(width / seed, 0f, width / seed, previewHeight.toFloat(), paint)
canvas.drawLine(width / seed, 0f, width / seed, height.toFloat(), paint)
canvas.drawLine(
0f, previewHeight / seed * 2f,
width.toFloat(), previewHeight / seed * 2f, paint
0f, height / seed * 2f,
width.toFloat(), height / seed * 2f, paint
)
canvas.drawLine(0f, previewHeight / seed, width.toFloat(), previewHeight / seed, paint)
canvas.drawLine(0f, height / seed, width.toFloat(), height / seed, paint)

if (seed == 4f) {
canvas.drawLine(
width / seed * 3f,
0f,
width / seed * 3f,
previewHeight.toFloat(),
height.toFloat(),
paint
)
canvas.drawLine(
0f, previewHeight / seed * 3f,
width.toFloat(), previewHeight / seed * 3f, paint
0f, height / seed * 3f,
width.toFloat(), height / seed * 3f, paint
)
}
}
Expand Down
77 changes: 44 additions & 33 deletions app/src/main/java/app/grapheneos/camera/ui/SettingsDialog.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import android.app.Dialog
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.Rect
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Gravity
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
Expand All @@ -20,6 +22,7 @@ import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RadioGroup
Expand Down Expand Up @@ -86,10 +89,9 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) :

var settingsFrame: View

// Window the panel region was last sized for, so the sizing runs on a rotation and not on
// every frame the panel is drawn in.
private var sizedForWindowWidth = 0
private var sizedForWindowHeight = 0
// Region the panel was last sized for, so the sizing runs when it moves and not on every
// frame the panel is drawn in.
private val sizedForRegion = Rect()

private var moreSettingsButton: View

Expand Down Expand Up @@ -160,6 +162,12 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) :

binding.root.viewTreeObserver.addOnPreDrawListener { updatePanelRegion() }

// The preview belongs to the activity's window, so resizing it schedules no traversal in
// this one: without this the panel would keep the bounds of the preview it was opened over.
mActivity.previewView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updatePanelRegion()
}

locToggle = binding.locationToggle
locToggle.setOnClickListener {
if (mActivity.videoCapturer.isRecording) {
Expand Down Expand Up @@ -396,46 +404,49 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) :
}

/**
* Height of the region the panel is centred within. The panel belongs over the preview, whose
* height the 4:3 aspect ratio ties to the window width — but that only holds while the window
* is portrait. In landscape the product is taller than the window itself, which centres the
* panel past the bottom edge and leaves nothing on screen but the top of the toggle row, so
* never let the region outgrow the window it has to be drawn in.
* The preview's rectangle, in the dialog window's coordinates. The panel is centred within
* the preview, and reading the preview is what keeps that true at any window size and aspect
* ratio — recomputing the geometry here would only be a second copy of it, free to disagree.
*/
private fun panelRegionHeight(): Int {
val marginTop = (mActivity.rootView.layoutParams as ViewGroup.MarginLayoutParams)
.topMargin
private fun previewRegion(): Rect? {
val preview = mActivity.previewView
if (preview.width == 0 || preview.height == 0) {
return null
}

val previewRegion = marginTop + (binding.root.measuredWidth * 4 / 3)
val windowHeight = binding.root.measuredHeight
val previewLocation = IntArray(2)
preview.getLocationOnScreen(previewLocation)

return when {
windowHeight > 0 -> {
previewRegion.coerceAtMost(windowHeight)
}
else -> previewRegion
}
val rootLocation = IntArray(2)
binding.root.getLocationOnScreen(rootLocation)

val left = previewLocation[0] - rootLocation[0]
val top = previewLocation[1] - rootLocation[1]

return Rect(left, top, left + preview.width, top + preview.height)
}

/**
* Sizes the region to the window the panel is about to be drawn in, whenever that window is
* not the one it was last sized for. The activity declares orientation as a config change it
* handles itself rather than being recreated, so nothing else tells the panel it has been
* rotated — and it can be rotated while it is open, not only between [show]s.
* Moves the panel onto the preview whenever the preview is not where it was last sized for.
* The activity declares orientation as a config change it handles itself rather than being
* recreated, so nothing else tells the panel it has been rotated — and it can be rotated, or
* have its aspect ratio toggled, while it is open rather than between [show]s.
*/
private fun updatePanelRegion(): Boolean {
val width = binding.root.measuredWidth
val height = binding.root.measuredHeight
if (width == sizedForWindowWidth && height == sizedForWindowHeight) {
val region = previewRegion() ?: return true
if (region == sizedForRegion) {
return true
}

sizedForWindowWidth = width
sizedForWindowHeight = height
sizedForRegion.set(region)

settingsFrame.layoutParams = (settingsFrame.layoutParams as ViewGroup.MarginLayoutParams)
settingsFrame.layoutParams = (settingsFrame.layoutParams as FrameLayout.LayoutParams)
.also {
it.height = panelRegionHeight()
it.gravity = Gravity.TOP or Gravity.START
it.leftMargin = region.left
it.topMargin = region.top
it.width = region.width()
it.height = region.height()
}

// The list is capped against the region, so it has to be measured against the new one too.
Expand All @@ -460,8 +471,8 @@ class SettingsDialog(val mActivity: MainActivity, themedContext: Context) :
val totalDialogHeight = moreSettingsButton.height + moreSettingsButtonTopPadding +
dialog.height
val availableWidth = dialog.width - (settingsDialogHorizontalMargin * 4)
val availableHeight = availableWidth
.coerceAtMost(panelRegionHeight()) -
val regionHeight = previewRegion()?.height() ?: binding.root.measuredHeight
val availableHeight = availableWidth.coerceAtMost(regionHeight) -
(totalDialogHeight - mScrollView.height)

val height = if (mScrollViewContent.height < mScrollView.height) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.ImageDecoder
import android.graphics.Point
Expand Down Expand Up @@ -77,6 +78,7 @@ import app.grapheneos.camera.shareCapturedItem
import app.grapheneos.camera.databinding.ActivityMainBinding
import app.grapheneos.camera.databinding.ScanResultDialogBinding
import app.grapheneos.camera.ktx.SystemSettingsObserver
import app.grapheneos.camera.ktx.applyPreviewRatio
import app.grapheneos.camera.notifier.SensorOrientationChangeNotifier
import app.grapheneos.camera.ui.BottomTabLayout
import app.grapheneos.camera.ui.CountDownTimerUI
Expand Down Expand Up @@ -1303,7 +1305,7 @@ open class MainActivity : AppCompatActivity(),

if (videoCapturer.isRecording) return

var iconRotation = (360f - orientation) % 360
var iconRotation = (360f - ((orientation - getRotation() + 360) % 360)) % 360

// Rotate views that should rotate irrespective of the auto-rotate setting
rotateView(gCircleFrame, iconRotation)
Expand Down Expand Up @@ -1543,6 +1545,21 @@ open class MainActivity : AppCompatActivity(),
}
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)

// The activity declares configChanges for orientation, so nothing else refreshes
// rotation-dependent state.
// The preview follows the window; the capture use cases follow the sensor and are updated
// by onOrientationChange.
camConfig.preview?.targetRotation = previewView.display?.rotation ?: Surface.ROTATION_0
camConfig.camera?.cameraInfo?.let {
previewView.applyPreviewRatio(camConfig.aspectRatio, it)
}

rootView.post { sensorNotifier?.notifyListeners() }
}

private fun pauseOrientationSensor() {
SensorOrientationChangeNotifier
.getInstance(this)?.remove(this)
Expand Down
Loading