From 7e8c1d0900ca792f96de876f60e48fa2abd754f5 Mon Sep 17 00:00:00 2001 From: alexceend Date: Sat, 1 Aug 2026 16:30:26 +0200 Subject: [PATCH 01/26] Manual mode in BottomTabLayout --- app/src/main/java/app/grapheneos/camera/CamConfig.kt | 3 ++- app/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index d558133f7..02cc5ed02 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -76,6 +76,7 @@ enum class CameraMode(val extensionMode: Int, val uiName: Int) { HDR(ExtensionMode.HDR, R.string.hdr_mode), CAMERA(ExtensionMode.NONE, R.string.camera), VIDEO(ExtensionMode.NONE, R.string.video), + MANUAL(ExtensionMode.NONE, R.string.manual_mode) } @SuppressLint("UnsafeOptInUsageError") @@ -1860,7 +1861,7 @@ class CamConfig(private val mActivity: MainActivity) { private fun availableModes(): Set { return CameraMode.entries.filter { when (it) { - CameraMode.CAMERA, CameraMode.VIDEO -> true + CameraMode.CAMERA, CameraMode.VIDEO, CameraMode.MANUAL -> true CameraMode.QR_SCAN -> mActivity !is SecureMainActivity else -> { check(it.extensionMode != ExtensionMode.NONE) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8df97c257..6d64301fa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,6 +75,7 @@ FACE RETOUCH AUTO QR SCAN + MANUAL 16:9 4:3 Share Media From a31065f18a552b7888aa3e7335dbd3964ec15367 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 16:46:21 +0200 Subject: [PATCH 02/26] ISO button in manual mode --- .../java/app/grapheneos/camera/CamConfig.kt | 8 ++++++++ .../camera/ui/activities/MainActivity.kt | 3 +++ app/src/main/res/drawable/manual_button_bg.xml | 7 +++++++ app/src/main/res/layout/activity_main.xml | 18 ++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 5 files changed, 37 insertions(+) create mode 100644 app/src/main/res/drawable/manual_button_bg.xml diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 02cc5ed02..71e6081de 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -314,6 +314,9 @@ class CamConfig(private val mActivity: MainActivity) { var isQRMode = false private set + var isManualMode = false + private set + val isFlashAvailable: Boolean get() = camera?.cameraInfo?.hasFlashUnit() ?: false @@ -2014,6 +2017,8 @@ class CamConfig(private val mActivity: MainActivity) { isVideoMode = mode == CameraMode.VIDEO + isManualMode = mode == CameraMode.MANUAL + if (isQRMode) { mActivity.qrOverlay.visibility = View.VISIBLE mActivity.thirdOption.visibility = View.INVISIBLE @@ -2037,6 +2042,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.setCaptureButtonIcon(R.drawable.torch_off_button, R.string.turn_torch_on) mActivity.micOffIcon.visibility = View.GONE + mActivity.isoButton.visibility = View.GONE } else { mActivity.qrOverlay.visibility = View.INVISIBLE mActivity.thirdOption.visibility = View.VISIBLE @@ -2053,6 +2059,8 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.setCaptureButtonIcon(R.drawable.camera_shutter, R.string.capture) mActivity.micOffIcon.visibility = View.GONE } + + mActivity.isoButton.visibility = if (isManualMode) View.VISIBLE else View.GONE } mActivity.updateSelfTimerBadge() diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 5412822c2..7358e889b 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -248,6 +248,7 @@ open class MainActivity : AppCompatActivity(), } lateinit var micOffIcon: ImageView + lateinit var isoButton: TextView private var shouldRestartRecording = false @@ -923,6 +924,8 @@ open class MainActivity : AppCompatActivity(), micOffIcon = binding.micOff + isoButton = binding.isoButton + previewView.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { diff --git a/app/src/main/res/drawable/manual_button_bg.xml b/app/src/main/res/drawable/manual_button_bg.xml new file mode 100644 index 000000000..35fa7a713 --- /dev/null +++ b/app/src/main/res/drawable/manual_button_bg.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b2aa0db88..07cd7519c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -215,6 +215,24 @@ + + More Options PDF 417 More Settings + ISO General From 7ffdfd060c26b4b5dbc7c44e98d42e02aeeca2d3 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 16:48:43 +0200 Subject: [PATCH 03/26] Added onClickListener to ISO button --- .../kotlin-compiler-11048223272619386759.salive | 0 .../grapheneos/camera/ui/activities/MainActivity.kt | 13 +++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 .kotlin/sessions/kotlin-compiler-11048223272619386759.salive diff --git a/.kotlin/sessions/kotlin-compiler-11048223272619386759.salive b/.kotlin/sessions/kotlin-compiler-11048223272619386759.salive new file mode 100644 index 000000000..e69de29bb diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 7358e889b..550d32883 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -11,6 +11,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.content.res.Configuration import android.graphics.Bitmap +import android.graphics.Color import android.graphics.ImageDecoder import android.graphics.Point import android.graphics.Rect @@ -780,6 +781,18 @@ open class MainActivity : AppCompatActivity(), } } + isoButton.setOnClickListener { + it.isSelected = !it.isSelected + + if(it.isSelected){ + isoButton.setTextColor(Color.BLACK) + isoButton.setBackgroundColor(Color.WHITE) + }else{ + isoButton.setTextColor(Color.WHITE) + isoButton.setBackgroundColor(Color.BLACK) + } + } + cancelButtonView = binding.cancelButton zoomBar = binding.zoomBar From 3ca0c530a13662078f54eb716e4c386fabb12ade Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 17:05:21 +0200 Subject: [PATCH 04/26] Crash FIX, now initializes before (ISO button) --- .kotlin/sessions/kotlin-compiler-11048223272619386759.salive | 0 .../java/app/grapheneos/camera/ui/activities/MainActivity.kt | 3 +-- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 .kotlin/sessions/kotlin-compiler-11048223272619386759.salive diff --git a/.kotlin/sessions/kotlin-compiler-11048223272619386759.salive b/.kotlin/sessions/kotlin-compiler-11048223272619386759.salive deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 550d32883..2ba577b3e 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -619,6 +619,7 @@ open class MainActivity : AppCompatActivity(), camConfig = CamConfig(this) cameraControl = CameraControl(camConfig) mainOverlay = binding.mainOverlay + isoButton = binding.isoButton imageCapturer = ImageCapturer(this) videoCapturer = VideoCapturer(this) thirdOption = binding.thirdOption @@ -937,8 +938,6 @@ open class MainActivity : AppCompatActivity(), micOffIcon = binding.micOff - isoButton = binding.isoButton - previewView.viewTreeObserver.addOnPreDrawListener( object : ViewTreeObserver.OnPreDrawListener { override fun onPreDraw(): Boolean { From be937745b8428b76481db3c8537ca0cb2a407c8a Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 17:08:03 +0200 Subject: [PATCH 05/26] Created xml reource for pressed button --- .../sessions/kotlin-compiler-14337055153839332662.salive | 0 .../app/grapheneos/camera/ui/activities/MainActivity.kt | 8 ++++---- app/src/main/res/drawable/manual_button_pressed_bg.xml | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 .kotlin/sessions/kotlin-compiler-14337055153839332662.salive create mode 100644 app/src/main/res/drawable/manual_button_pressed_bg.xml diff --git a/.kotlin/sessions/kotlin-compiler-14337055153839332662.salive b/.kotlin/sessions/kotlin-compiler-14337055153839332662.salive new file mode 100644 index 000000000..e69de29bb diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 2ba577b3e..c7fa1fac3 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -785,12 +785,12 @@ open class MainActivity : AppCompatActivity(), isoButton.setOnClickListener { it.isSelected = !it.isSelected - if(it.isSelected){ + if (it.isSelected) { + isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) isoButton.setTextColor(Color.BLACK) - isoButton.setBackgroundColor(Color.WHITE) - }else{ + } else { + isoButton.setBackgroundResource(R.drawable.manual_button_bg) isoButton.setTextColor(Color.WHITE) - isoButton.setBackgroundColor(Color.BLACK) } } diff --git a/app/src/main/res/drawable/manual_button_pressed_bg.xml b/app/src/main/res/drawable/manual_button_pressed_bg.xml new file mode 100644 index 000000000..fe3ed9878 --- /dev/null +++ b/app/src/main/res/drawable/manual_button_pressed_bg.xml @@ -0,0 +1,7 @@ + + + + + + From 1cb20f5c4a9e690235d1994e87e6143b22cabe59 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 17:24:06 +0200 Subject: [PATCH 06/26] Created Layout for iso slider --- ...otlin-compiler-14337055153839332662.salive | 0 app/src/main/res/layout/activity_main.xml | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+) delete mode 100644 .kotlin/sessions/kotlin-compiler-14337055153839332662.salive diff --git a/.kotlin/sessions/kotlin-compiler-14337055153839332662.salive b/.kotlin/sessions/kotlin-compiler-14337055153839332662.salive deleted file mode 100644 index e69de29bb..000000000 diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 07cd7519c..179e666b7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -215,6 +215,27 @@ + + + + + Date: Mon, 3 Aug 2026 17:38:50 +0200 Subject: [PATCH 07/26] ISO bar now visible onclick --- .../camera/ui/activities/MainActivity.kt | 8 +++++ .../grapheneos/camera/ui/seekbar/IsoBar.kt | 34 +++++++++++++++++++ app/src/main/res/layout/activity_main.xml | 2 +- 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index c7fa1fac3..62553884c 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -44,6 +44,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.RelativeLayout +import android.widget.SeekBar import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -251,6 +252,9 @@ open class MainActivity : AppCompatActivity(), lateinit var micOffIcon: ImageView lateinit var isoButton: TextView + private lateinit var isoSeekBar: SeekBar + private lateinit var isoSliderContainer: View + private var shouldRestartRecording = false fun startFocusTimer() { @@ -620,6 +624,8 @@ open class MainActivity : AppCompatActivity(), cameraControl = CameraControl(camConfig) mainOverlay = binding.mainOverlay isoButton = binding.isoButton + isoSeekBar = binding.isoSeekbar + isoSliderContainer = binding.isoSliderContainer imageCapturer = ImageCapturer(this) videoCapturer = VideoCapturer(this) thirdOption = binding.thirdOption @@ -788,9 +794,11 @@ open class MainActivity : AppCompatActivity(), if (it.isSelected) { isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) isoButton.setTextColor(Color.BLACK) + isoSliderContainer.visibility = View.VISIBLE } else { isoButton.setBackgroundResource(R.drawable.manual_button_bg) isoButton.setTextColor(Color.WHITE) + isoSliderContainer.visibility = View.GONE } } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt new file mode 100644 index 000000000..2909ae05d --- /dev/null +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -0,0 +1,34 @@ +package app.grapheneos.camera.ui.seekbar + +import android.content.Context +import android.util.AttributeSet +import android.widget.SeekBar +import androidx.appcompat.widget.AppCompatSeekBar +import app.grapheneos.camera.ui.activities.MainActivity + +class IsoBar : AppCompatSeekBar { + + + private val isoValues = arrayOf(50, 100, 200, 400, 800, 1600) + private lateinit var mainActivity: MainActivity + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + + fun setMainActivity(mainActivity: MainActivity) { + this.mainActivity = mainActivity + this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val selectedIso = isoValues[progress] + + // Future implementation of + // camConfig.setISO(selectedIso) + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + }) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 179e666b7..c0ab19f08 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -226,7 +226,7 @@ android:padding="12dp" android:visibility="gone"> - Date: Mon, 3 Aug 2026 17:56:32 +0200 Subject: [PATCH 08/26] Beautify iso slider and added more values --- .../camera/ui/activities/MainActivity.kt | 10 +++++++-- .../grapheneos/camera/ui/seekbar/IsoBar.kt | 18 +++++++++++---- app/src/main/res/layout/activity_main.xml | 22 ++++++++++++++++++- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 62553884c..55680a6c1 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -89,6 +89,7 @@ import app.grapheneos.camera.ui.QROverlay import app.grapheneos.camera.ui.QRToggle import app.grapheneos.camera.ui.SettingsDialog import app.grapheneos.camera.ui.seekbar.ExposureBar +import app.grapheneos.camera.ui.seekbar.IsoBar import app.grapheneos.camera.ui.seekbar.ZoomBar import app.grapheneos.camera.ui.showIgnoringShortEdgeMode import app.grapheneos.camera.util.CameraControl @@ -252,8 +253,9 @@ open class MainActivity : AppCompatActivity(), lateinit var micOffIcon: ImageView lateinit var isoButton: TextView - private lateinit var isoSeekBar: SeekBar - private lateinit var isoSliderContainer: View + lateinit var isoSeekBar: IsoBar + lateinit var isoSliderContainer: View + lateinit var isoValueText: TextView private var shouldRestartRecording = false @@ -624,8 +626,12 @@ open class MainActivity : AppCompatActivity(), cameraControl = CameraControl(camConfig) mainOverlay = binding.mainOverlay isoButton = binding.isoButton + isoSeekBar = binding.isoSeekbar + isoSeekBar.setMainActivity(this) + isoSliderContainer = binding.isoSliderContainer + isoValueText = binding.isoValueText imageCapturer = ImageCapturer(this) videoCapturer = VideoCapturer(this) thirdOption = binding.thirdOption diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index 2909ae05d..e0da9cf19 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -9,7 +9,13 @@ import app.grapheneos.camera.ui.activities.MainActivity class IsoBar : AppCompatSeekBar { - private val isoValues = arrayOf(50, 100, 200, 400, 800, 1600) + private val isoValues = arrayOf( + 50, 100, 125, 150, 175, + 200, 225, 250, 275, + 300, 325, 350, 375, + 400, 425, 450, 475, + 500, 600, 700, 800 + ) private lateinit var mainActivity: MainActivity constructor(context: Context) : super(context) @@ -21,10 +27,14 @@ class IsoBar : AppCompatSeekBar { this.mainActivity = mainActivity this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - val selectedIso = isoValues[progress] + if(progress < isoValues.size){ + val selectedIso = isoValues[progress] - // Future implementation of - // camConfig.setISO(selectedIso) + mainActivity.isoValueText.text = selectedIso.toString(); + + // Future implementation of + // camConfig.setISO(selectedIso) + } } override fun onStartTrackingTouch(seekBar: SeekBar?) {} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index c0ab19f08..e969f702b 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -219,6 +219,9 @@ android:id="@+id/iso_slider_container" android:layout_width="280dp" android:layout_height="wrap_content" + android:paddingTop="24dp" + android:paddingBottom="12dp" + android:paddingHorizontal="12dp" android:layout_above="@id/iso_button" android:layout_centerHorizontal="true" android:layout_marginBottom="12dp" @@ -226,11 +229,28 @@ android:padding="12dp" android:visibility="gone"> + + From d4cd9f8fa6584116a8d390d15cef62e1ae9e2d20 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 18:33:44 +0200 Subject: [PATCH 09/26] ISO now uses min & max and if given -1 value it returns to AE mode ON for camera mode --- .idea/deviceManager.xml | 13 ++++++ .idea/misc.xml | 2 +- .../java/app/grapheneos/camera/CamConfig.kt | 36 +++++++++++++++ .../camera/ui/activities/MainActivity.kt | 4 ++ .../grapheneos/camera/ui/seekbar/IsoBar.kt | 46 ++++++++++++++----- app/src/main/res/layout/activity_main.xml | 2 +- 6 files changed, 90 insertions(+), 13 deletions(-) create mode 100644 .idea/deviceManager.xml diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 000000000..91f95584d --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 84db41232..d2f3f09f4 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -120,7 +120,7 @@ - + diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 71e6081de..7f7dc430c 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -65,6 +65,10 @@ import com.google.zxing.BarcodeFormat import java.util.concurrent.ExecutionException import java.util.concurrent.Executors import kotlin.concurrent.thread +import androidx.camera.camera2.interop.Camera2CameraControl +import androidx.camera.camera2.interop.CaptureRequestOptions +import android.hardware.camera2.CaptureRequest +import android.util.Range // note that enum constant name is used as a name of a SharedPreferences instance enum class CameraMode(val extensionMode: Int, val uiName: Int) { @@ -1020,6 +1024,30 @@ class CamConfig(private val mActivity: MainActivity) { } } + @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) + fun getIsoRange(): Range? { + val cameraInfo = camera?.cameraInfo ?: return null + return Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) + } + + @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) + fun setISO(isoValue: Int) { + val cameraControl = camera?.cameraControl ?: return + val camera2CameraControl = Camera2CameraControl.from(cameraControl) + + val builder = CaptureRequestOptions.Builder() + + if (isoValue == -1) { + builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) + } else { + builder.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, isoValue) + builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) + } + + camera2CameraControl.setCaptureRequestOptions(builder.build()) + } + fun toggleAspectRatio() { aspectRatio = if (aspectRatio == AspectRatio.RATIO_16_9) { AspectRatio.RATIO_4_3 @@ -2043,6 +2071,9 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.micOffIcon.visibility = View.GONE mActivity.isoButton.visibility = View.GONE + mActivity.isoButton.isSelected = false + mActivity.isoSliderContainer.visibility = View.GONE + setISO(-1) } else { mActivity.qrOverlay.visibility = View.INVISIBLE mActivity.thirdOption.visibility = View.VISIBLE @@ -2061,6 +2092,11 @@ class CamConfig(private val mActivity: MainActivity) { } mActivity.isoButton.visibility = if (isManualMode) View.VISIBLE else View.GONE + if (!isManualMode) { + mActivity.isoButton.isSelected = false + mActivity.isoSliderContainer.visibility = View.GONE + setISO(-1) + } } mActivity.updateSelfTimerBadge() diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 55680a6c1..afc508d12 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -660,6 +660,7 @@ open class MainActivity : AppCompatActivity(), if (state == StreamState.STREAMING) { mainOverlay.visibility = View.INVISIBLE camConfig.reloadSettings() + isoSeekBar.refreshIsoValues() if (!camConfig.isQRMode) { previewGrid.visibility = View.VISIBLE if (!settingsDialog.isShowing) { @@ -801,10 +802,13 @@ open class MainActivity : AppCompatActivity(), isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) isoButton.setTextColor(Color.BLACK) isoSliderContainer.visibility = View.VISIBLE + camConfig.setISO(isoSeekBar.getCurrentIsoValue()) } else { isoButton.setBackgroundResource(R.drawable.manual_button_bg) isoButton.setTextColor(Color.WHITE) isoSliderContainer.visibility = View.GONE + // Reset to Auto Exposure when closing the slider + camConfig.setISO(-1) } } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index e0da9cf19..3f01c6ffc 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -9,13 +9,7 @@ import app.grapheneos.camera.ui.activities.MainActivity class IsoBar : AppCompatSeekBar { - private val isoValues = arrayOf( - 50, 100, 125, 150, 175, - 200, 225, 250, 275, - 300, 325, 350, 375, - 400, 425, 450, 475, - 500, 600, 700, 800 - ) + private var isoValues: List = listOf(50, 100, 200, 400, 800, 1600) private lateinit var mainActivity: MainActivity constructor(context: Context) : super(context) @@ -25,15 +19,22 @@ class IsoBar : AppCompatSeekBar { fun setMainActivity(mainActivity: MainActivity) { this.mainActivity = mainActivity + + val range = mainActivity.camConfig.getIsoRange() + + if (range != null) { + val minIso = range.lower + val maxIso = range.upper + isoValues = listOf(minIso, 100, 200, 400, 800, 1600, maxIso).distinct().sorted() + } + this.max = isoValues.size - 1 + this.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { if(progress < isoValues.size){ val selectedIso = isoValues[progress] - mainActivity.isoValueText.text = selectedIso.toString(); - - // Future implementation of - // camConfig.setISO(selectedIso) + mainActivity.camConfig.setISO(selectedIso) } } @@ -41,4 +42,27 @@ class IsoBar : AppCompatSeekBar { override fun onStopTrackingTouch(seekBar: SeekBar?) {} }) } + + fun refreshIsoValues() { + val range = mainActivity.camConfig.getIsoRange() + if (range != null) { + val minIso = range.lower + val maxIso = range.upper + isoValues = listOf(minIso, 100, 200, 400, 800, 1600, maxIso).distinct().sorted() + this.max = isoValues.size - 1 + + // Sync UI + if (this.progress >= isoValues.size) { + this.progress = 0 + } + val selectedIso = isoValues[this.progress] + mainActivity.isoValueText.text = selectedIso.toString() + mainActivity.isoButton.text = "ISO $selectedIso" + } + } + + fun getCurrentIsoValue(): Int { + return if (progress < isoValues.size) isoValues[progress] else isoValues.last() + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index e969f702b..676d7570e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -251,7 +251,7 @@ android:layout_height="wrap_content" android:layout_marginTop="18dp" android:max="21" - android:progress="3" + android:progress="1" android:thumbTint="@android:color/white" android:progressTint="@android:color/white" /> From b7df8b18b8c66ddc2a1cc058994dfddfc4e4ea17 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 18:43:50 +0200 Subject: [PATCH 10/26] AE returns to ON when MANUAL mode is not selected and changed setIso for applyManualSettings --- .../java/app/grapheneos/camera/CamConfig.kt | 27 ++++++++++++++----- .../camera/ui/activities/MainActivity.kt | 21 ++++++++++----- .../grapheneos/camera/ui/seekbar/IsoBar.kt | 14 ++++++++-- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 7f7dc430c..3a91a2207 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -279,6 +279,9 @@ class CamConfig(private val mActivity: MainActivity) { var lastCapturedItem: CapturedItem? = null + var manualIsoValue: Int? = null + var manualExposureValue: Int? = null + init { if (mActivity !is SecureActivity) { CapturedItems.init(mActivity, this) @@ -1032,22 +1035,28 @@ class CamConfig(private val mActivity: MainActivity) { } @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) - fun setISO(isoValue: Int) { + fun applyManualSettings() { val cameraControl = camera?.cameraControl ?: return val camera2CameraControl = Camera2CameraControl.from(cameraControl) - val builder = CaptureRequestOptions.Builder() - if (isoValue == -1) { + // Lógica inteligente de AE Mode + if (manualIsoValue == null && manualExposureValue == null) { builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) } else { - builder.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, isoValue) builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) + manualIsoValue?.let { + builder.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, it) + } + manualExposureValue?.let { + builder.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, it.toLong()) + } } camera2CameraControl.setCaptureRequestOptions(builder.build()) } + fun toggleAspectRatio() { aspectRatio = if (aspectRatio == AspectRatio.RATIO_16_9) { AspectRatio.RATIO_4_3 @@ -2073,7 +2082,10 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.isoButton.visibility = View.GONE mActivity.isoButton.isSelected = false mActivity.isoSliderContainer.visibility = View.GONE - setISO(-1) + mActivity.updateIsoButtonUI() + manualIsoValue = null + manualExposureValue = null + applyManualSettings() } else { mActivity.qrOverlay.visibility = View.INVISIBLE mActivity.thirdOption.visibility = View.VISIBLE @@ -2095,7 +2107,10 @@ class CamConfig(private val mActivity: MainActivity) { if (!isManualMode) { mActivity.isoButton.isSelected = false mActivity.isoSliderContainer.visibility = View.GONE - setISO(-1) + mActivity.updateIsoButtonUI() + manualIsoValue = null + manualExposureValue = null + applyManualSettings() } } diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index afc508d12..7e1952cde 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -257,6 +257,16 @@ open class MainActivity : AppCompatActivity(), lateinit var isoSliderContainer: View lateinit var isoValueText: TextView + fun updateIsoButtonUI() { + if (isoButton.isSelected) { + isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) + isoButton.setTextColor(Color.BLACK) + } else { + isoButton.setBackgroundResource(R.drawable.manual_button_bg) + isoButton.setTextColor(Color.WHITE) + } + } + private var shouldRestartRecording = false fun startFocusTimer() { @@ -797,19 +807,18 @@ open class MainActivity : AppCompatActivity(), isoButton.setOnClickListener { it.isSelected = !it.isSelected + updateIsoButtonUI() if (it.isSelected) { - isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) - isoButton.setTextColor(Color.BLACK) isoSliderContainer.visibility = View.VISIBLE - camConfig.setISO(isoSeekBar.getCurrentIsoValue()) + // Apply current ISO when opening the slider + camConfig.manualIsoValue = isoSeekBar.getCurrentIsoValue() } else { - isoButton.setBackgroundResource(R.drawable.manual_button_bg) - isoButton.setTextColor(Color.WHITE) isoSliderContainer.visibility = View.GONE // Reset to Auto Exposure when closing the slider - camConfig.setISO(-1) + camConfig.manualIsoValue = null } + camConfig.applyManualSettings() } cancelButtonView = binding.cancelButton diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index 3f01c6ffc..a3a6a59a5 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -34,7 +34,12 @@ class IsoBar : AppCompatSeekBar { if(progress < isoValues.size){ val selectedIso = isoValues[progress] mainActivity.isoValueText.text = selectedIso.toString(); - mainActivity.camConfig.setISO(selectedIso) + + // If manual button is active, apply the setting + if (mainActivity.isoButton.isSelected) { + mainActivity.camConfig.manualIsoValue = selectedIso + mainActivity.camConfig.applyManualSettings() + } } } @@ -57,7 +62,12 @@ class IsoBar : AppCompatSeekBar { } val selectedIso = isoValues[this.progress] mainActivity.isoValueText.text = selectedIso.toString() - mainActivity.isoButton.text = "ISO $selectedIso" + + // Sync current ISO to config if manual mode is active + if (mainActivity.isoButton.isSelected) { + mainActivity.camConfig.manualIsoValue = selectedIso + mainActivity.camConfig.applyManualSettings() + } } } From eb8e262be2f68bb6a87d8b41a00b38721df900aa Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 18:48:20 +0200 Subject: [PATCH 11/26] FIX: Now closing ISO slidebar won't remove the selected value --- .../app/grapheneos/camera/ui/activities/MainActivity.kt | 9 ++++----- .../main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt | 7 ++----- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 7e1952cde..55bb47366 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -811,14 +811,13 @@ open class MainActivity : AppCompatActivity(), if (it.isSelected) { isoSliderContainer.visibility = View.VISIBLE - // Apply current ISO when opening the slider - camConfig.manualIsoValue = isoSeekBar.getCurrentIsoValue() + if (camConfig.manualIsoValue == null) { + camConfig.manualIsoValue = isoSeekBar.getCurrentIsoValue() + camConfig.applyManualSettings() + } } else { isoSliderContainer.visibility = View.GONE - // Reset to Auto Exposure when closing the slider - camConfig.manualIsoValue = null } - camConfig.applyManualSettings() } cancelButtonView = binding.cancelButton diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index a3a6a59a5..56f465011 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -35,11 +35,8 @@ class IsoBar : AppCompatSeekBar { val selectedIso = isoValues[progress] mainActivity.isoValueText.text = selectedIso.toString(); - // If manual button is active, apply the setting - if (mainActivity.isoButton.isSelected) { - mainActivity.camConfig.manualIsoValue = selectedIso - mainActivity.camConfig.applyManualSettings() - } + mainActivity.camConfig.manualIsoValue = selectedIso + mainActivity.camConfig.applyManualSettings() } } From f2e8f5acef388b0bcd9da83a20b057438bf80867 Mon Sep 17 00:00:00 2001 From: alexceend Date: Mon, 3 Aug 2026 19:03:58 +0200 Subject: [PATCH 12/26] Made shutter speed functionality --- .../java/app/grapheneos/camera/CamConfig.kt | 12 +++ .../camera/ui/activities/MainActivity.kt | 32 ++++++++ .../camera/ui/seekbar/ShutterSpeedBar.kt | 71 +++++++++++++++++ app/src/main/res/layout/activity_main.xml | 76 ++++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 5 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 3a91a2207..ad3c8164e 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -1034,6 +1034,13 @@ class CamConfig(private val mActivity: MainActivity) { .getCameraCharacteristic(CameraCharacteristics.SENSOR_INFO_SENSITIVITY_RANGE) } + @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) + fun getShutterRange(): Range? { + val cameraInfo = camera?.cameraInfo ?: return null + return Camera2CameraInfo.from(cameraInfo) + .getCameraCharacteristic(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) + } + @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) fun applyManualSettings() { val cameraControl = camera?.cameraControl ?: return @@ -2104,9 +2111,14 @@ class CamConfig(private val mActivity: MainActivity) { } mActivity.isoButton.visibility = if (isManualMode) View.VISIBLE else View.GONE + mActivity.shutterButton.visibility = if (isManualMode) View.VISIBLE else View.GONE if (!isManualMode) { mActivity.isoButton.isSelected = false mActivity.isoSliderContainer.visibility = View.GONE + + mActivity.shutterButton.isSelected = false + mActivity.shutterSpeedSliderContainer.visibility = View.GONE + mActivity.updateIsoButtonUI() manualIsoValue = null manualExposureValue = null diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 55bb47366..ff625baf5 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -90,6 +90,7 @@ import app.grapheneos.camera.ui.QRToggle import app.grapheneos.camera.ui.SettingsDialog import app.grapheneos.camera.ui.seekbar.ExposureBar import app.grapheneos.camera.ui.seekbar.IsoBar +import app.grapheneos.camera.ui.seekbar.ShutterSpeedBar import app.grapheneos.camera.ui.seekbar.ZoomBar import app.grapheneos.camera.ui.showIgnoringShortEdgeMode import app.grapheneos.camera.util.CameraControl @@ -257,6 +258,11 @@ open class MainActivity : AppCompatActivity(), lateinit var isoSliderContainer: View lateinit var isoValueText: TextView + lateinit var shutterButton: TextView + lateinit var shutterSpeedBar: ShutterSpeedBar + lateinit var shutterSpeedSliderContainer: View + lateinit var shutterSpeedValueText: TextView + fun updateIsoButtonUI() { if (isoButton.isSelected) { isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) @@ -642,6 +648,16 @@ open class MainActivity : AppCompatActivity(), isoSliderContainer = binding.isoSliderContainer isoValueText = binding.isoValueText + + shutterButton = binding.shutterButton + + shutterSpeedBar = binding.shutterSpeedSeekbar + shutterSpeedBar.setMainActivity(this) + + shutterSpeedSliderContainer = binding.shutterSpeedSliderContainer + shutterSpeedValueText = binding.shutterSpeedValueText + + imageCapturer = ImageCapturer(this) videoCapturer = VideoCapturer(this) thirdOption = binding.thirdOption @@ -820,6 +836,22 @@ open class MainActivity : AppCompatActivity(), } } + shutterButton.setOnClickListener { + it.isSelected = !it.isSelected + if (it.isSelected) { + shutterSpeedSliderContainer.visibility = View.VISIBLE + + isoButton.isSelected = false + isoSliderContainer.visibility = View.GONE + updateIsoButtonUI() + + camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue().toInt() + } else { + shutterSpeedSliderContainer.visibility = View.GONE + } + camConfig.applyManualSettings() + } + cancelButtonView = binding.cancelButton zoomBar = binding.zoomBar diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt new file mode 100644 index 000000000..4ca8e4f1a --- /dev/null +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -0,0 +1,71 @@ +package app.grapheneos.camera.ui.seekbar + +import android.content.Context +import android.util.AttributeSet +import android.widget.SeekBar +import androidx.appcompat.widget.AppCompatSeekBar +import app.grapheneos.camera.ui.activities.MainActivity + +class ShutterSpeedBar : AppCompatSeekBar { + + // Valores en segundos (Double) para generar los nanosegundos reales + private val shutterSpeeds = listOf( + 1/1000.0, 1/500.0, 1/250.0, 1/125.0, 1/60.0, 1/30.0, + 1/15.0, 1/8.0, 1/4.0, 1/2.0, 1.0, 2.0 + ) + + private var shutterValues: List = emptyList() + private lateinit var mainActivity: MainActivity + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + fun setMainActivity(mainActivity: MainActivity) { + this.mainActivity = mainActivity + refreshShutterValues() + + this.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + if (progress < shutterValues.size) { + val selectedNanos = shutterValues[progress] + + // Actualizamos UI con formato legible + val label = formatShutterSpeed(shutterSpeeds[progress]) + mainActivity.shutterSpeedValueText.text = label + mainActivity.shutterSpeedValueText.text = label + + // Aplicamos a la cámara + mainActivity.camConfig.manualExposureValue = selectedNanos.toInt() // Nota: CamConfig usa Int o Long + mainActivity.camConfig.applyManualSettings() + } + } + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + }) + } + + fun refreshShutterValues() { + val range = mainActivity.camConfig.getShutterRange() + val minNanos = range?.lower ?: 100_000L // 1/10000s por defecto + val maxNanos = range?.upper ?: 1_000_000_000L // 1s por defecto + + // Convertimos nuestra lista de segundos a nanosegundos (1s = 10^9 ns) + shutterValues = shutterSpeeds.map { (it * 1_000_000_000L).toLong() } + .filter { it in minNanos..maxNanos } + + this.max = shutterValues.size - 1 + } + + private fun formatShutterSpeed(seconds: Double): String { + return if (seconds < 1.0) { + "1/${(1.0 / seconds).toInt()}" + } else { + "${seconds.toInt()}s" + } + } + + fun getCurrentShutterValue(): Long { + return if (progress < shutterValues.size) shutterValues[progress] else shutterValues.last() + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 676d7570e..beed91602 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -222,7 +222,7 @@ android:paddingTop="24dp" android:paddingBottom="12dp" android:paddingHorizontal="12dp" - android:layout_above="@id/iso_button" + android:layout_above="@id/manual_controls_layout" android:layout_centerHorizontal="true" android:layout_marginBottom="12dp" android:background="@drawable/manual_button_bg" @@ -256,7 +256,56 @@ android:progressTint="@android:color/white" /> - + + + + + + + + + + + + + + + PDF 417 More Settings ISO + Shutter speed General From efd07332356366d7e4796403302094890e2aa4ad Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 12:49:22 +0200 Subject: [PATCH 13/26] [FIX] ISO button and Shutter button don't overlap now --- .../java/app/grapheneos/camera/CamConfig.kt | 4 +-- .../camera/ui/activities/MainActivity.kt | 31 +++++++++++++------ .../grapheneos/camera/ui/seekbar/IsoBar.kt | 6 ++-- app/src/main/res/layout/activity_main.xml | 9 +++--- app/src/main/res/values/strings.xml | 2 ++ 5 files changed, 33 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index ad3c8164e..e4f84a1de 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -2089,7 +2089,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.isoButton.visibility = View.GONE mActivity.isoButton.isSelected = false mActivity.isoSliderContainer.visibility = View.GONE - mActivity.updateIsoButtonUI() + //mActivity.updateIsoButtonUI() manualIsoValue = null manualExposureValue = null applyManualSettings() @@ -2119,7 +2119,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.shutterButton.isSelected = false mActivity.shutterSpeedSliderContainer.visibility = View.GONE - mActivity.updateIsoButtonUI() + //mActivity.updateIsoButtonUI() manualIsoValue = null manualExposureValue = null applyManualSettings() diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index ff625baf5..6ad5fd90d 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -263,13 +263,21 @@ open class MainActivity : AppCompatActivity(), lateinit var shutterSpeedSliderContainer: View lateinit var shutterSpeedValueText: TextView - fun updateIsoButtonUI() { - if (isoButton.isSelected) { - isoButton.setBackgroundResource(R.drawable.manual_button_pressed_bg) - isoButton.setTextColor(Color.BLACK) + + fun updateManualButtonsUI(){ + val buttons : List = listOf(isoButton, shutterButton) + for(b in buttons){ + updateManualButtonUI(b) + } + } + + private fun updateManualButtonUI(button: TextView) { + if (button.isSelected) { + button.setBackgroundResource(R.drawable.manual_button_pressed_bg) + button.setTextColor(Color.BLACK) } else { - isoButton.setBackgroundResource(R.drawable.manual_button_bg) - isoButton.setTextColor(Color.WHITE) + button.setBackgroundResource(R.drawable.manual_button_bg) + button.setTextColor(Color.WHITE) } } @@ -823,33 +831,38 @@ open class MainActivity : AppCompatActivity(), isoButton.setOnClickListener { it.isSelected = !it.isSelected - updateIsoButtonUI() if (it.isSelected) { + + shutterButton.isSelected = false; + shutterSpeedSliderContainer.visibility = View.GONE; + isoSliderContainer.visibility = View.VISIBLE if (camConfig.manualIsoValue == null) { camConfig.manualIsoValue = isoSeekBar.getCurrentIsoValue() - camConfig.applyManualSettings() } } else { isoSliderContainer.visibility = View.GONE } + camConfig.applyManualSettings() + updateManualButtonsUI() } shutterButton.setOnClickListener { it.isSelected = !it.isSelected + if (it.isSelected) { shutterSpeedSliderContainer.visibility = View.VISIBLE isoButton.isSelected = false isoSliderContainer.visibility = View.GONE - updateIsoButtonUI() camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue().toInt() } else { shutterSpeedSliderContainer.visibility = View.GONE } camConfig.applyManualSettings() + updateManualButtonsUI() } cancelButtonView = binding.cancelButton diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index 56f465011..bf92653eb 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -52,15 +52,13 @@ class IsoBar : AppCompatSeekBar { val maxIso = range.upper isoValues = listOf(minIso, 100, 200, 400, 800, 1600, maxIso).distinct().sorted() this.max = isoValues.size - 1 - - // Sync UI + if (this.progress >= isoValues.size) { this.progress = 0 } val selectedIso = isoValues[this.progress] mainActivity.isoValueText.text = selectedIso.toString() - - // Sync current ISO to config if manual mode is active + if (mainActivity.isoButton.isSelected) { mainActivity.camConfig.manualIsoValue = selectedIso mainActivity.camConfig.applyManualSettings() diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index beed91602..3974311c9 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -234,7 +234,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|center_horizontal" - android:text="100" + android:text="@string/default_iso_value" android:textColor="@android:color/white" android:textSize="12sp" android:textStyle="bold" @@ -260,8 +260,9 @@ android:id="@+id/shutter_speed_slider_container" android:layout_width="280dp" android:layout_height="wrap_content" - android:paddingTop="24dp" - android:paddingBottom="12dp" + android:layout_marginTop="24dp" + android:paddingTop="18dp" + android:paddingBottom="8dp" android:paddingHorizontal="12dp" android:layout_above="@id/manual_controls_layout" android:layout_centerHorizontal="true" @@ -275,7 +276,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top|center_horizontal" - android:text="100" + android:text="@string/default_shutter_speed_value" android:textColor="@android:color/white" android:textSize="12sp" android:textStyle="bold" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b835fb7d..cf189b55a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -88,7 +88,9 @@ PDF 417 More Settings ISO + 100 Shutter speed + 100 General From a20bf8cc80ad32ec4c44077d6715f7683b395208 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 12:58:02 +0200 Subject: [PATCH 14/26] Improved scalability of manual mode, now there's a list of buttons and methods to deselect all --- .../java/app/grapheneos/camera/CamConfig.kt | 8 +---- .../camera/ui/activities/MainActivity.kt | 32 +++++++++++++------ 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index e4f84a1de..692e17e0b 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -2113,13 +2113,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.isoButton.visibility = if (isManualMode) View.VISIBLE else View.GONE mActivity.shutterButton.visibility = if (isManualMode) View.VISIBLE else View.GONE if (!isManualMode) { - mActivity.isoButton.isSelected = false - mActivity.isoSliderContainer.visibility = View.GONE - - mActivity.shutterButton.isSelected = false - mActivity.shutterSpeedSliderContainer.visibility = View.GONE - - //mActivity.updateIsoButtonUI() + mActivity.deselectAllManualControls() manualIsoValue = null manualExposureValue = null applyManualSettings() diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 6ad5fd90d..8e8c7e2bf 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -263,6 +263,13 @@ open class MainActivity : AppCompatActivity(), lateinit var shutterSpeedSliderContainer: View lateinit var shutterSpeedValueText: TextView + private val manualControlPairs by lazy { + listOf( + isoButton to isoSliderContainer, + shutterButton to shutterSpeedSliderContainer + ) + } + fun updateManualButtonsUI(){ val buttons : List = listOf(isoButton, shutterButton) @@ -830,13 +837,11 @@ open class MainActivity : AppCompatActivity(), } isoButton.setOnClickListener { - it.isSelected = !it.isSelected + val targetState = !it.isSelected + deselectAllManualControls() + it.isSelected = targetState if (it.isSelected) { - - shutterButton.isSelected = false; - shutterSpeedSliderContainer.visibility = View.GONE; - isoSliderContainer.visibility = View.VISIBLE if (camConfig.manualIsoValue == null) { camConfig.manualIsoValue = isoSeekBar.getCurrentIsoValue() @@ -849,14 +854,12 @@ open class MainActivity : AppCompatActivity(), } shutterButton.setOnClickListener { - it.isSelected = !it.isSelected + val targetState = !it.isSelected + deselectAllManualControls() + it.isSelected = targetState if (it.isSelected) { shutterSpeedSliderContainer.visibility = View.VISIBLE - - isoButton.isSelected = false - isoSliderContainer.visibility = View.GONE - camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue().toInt() } else { shutterSpeedSliderContainer.visibility = View.GONE @@ -1891,6 +1894,15 @@ open class MainActivity : AppCompatActivity(), application.resetPreventScreenFromSleeping() } + fun deselectAllManualControls() { + manualControlPairs.forEach { (button, container) -> + button.isSelected = false + container.visibility = View.GONE + } + updateManualButtonsUI() + } + + @Volatile var isStarted = false override fun onStart() { From e45380764de1509e8df109c64846040638f20cb2 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 13:01:47 +0200 Subject: [PATCH 15/26] Centered manual mode buttons --- app/src/main/res/layout/activity_main.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3974311c9..49745019d 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -224,7 +224,7 @@ android:paddingHorizontal="12dp" android:layout_above="@id/manual_controls_layout" android:layout_centerHorizontal="true" - android:layout_marginBottom="12dp" + android:layout_marginBottom="8dp" android:background="@drawable/manual_button_bg" android:padding="12dp" android:visibility="gone"> @@ -266,7 +266,7 @@ android:paddingHorizontal="12dp" android:layout_above="@id/manual_controls_layout" android:layout_centerHorizontal="true" - android:layout_marginBottom="12dp" + android:layout_marginBottom="8dp" android:background="@drawable/manual_button_bg" android:padding="12dp" android:visibility="gone"> @@ -305,7 +305,7 @@ android:orientation="horizontal" android:layout_above="@id/three_buttons" android:layout_centerHorizontal="true" - android:layout_marginBottom="16dp"> + android:layout_marginBottom="6dp"> Date: Tue, 4 Aug 2026 18:19:05 +0200 Subject: [PATCH 16/26] Fixed AE mode not working on normal modes --- app/src/main/java/app/grapheneos/camera/CamConfig.kt | 4 ++++ .../main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt | 2 +- .../app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt | 8 ++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 692e17e0b..fdf9bc97f 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -1834,6 +1834,10 @@ class CamConfig(private val mActivity: MainActivity) { camera?.cameraInfo?.exposureState?.let { mActivity.exposureBar.setExposureConfig(it) } + if (isManualMode){ + applyManualSettings() + } + mActivity.settingsDialog.torchToggle.isChecked = false // Focus camera on touch/tap diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt index bf92653eb..f1fd2a91f 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/IsoBar.kt @@ -59,7 +59,7 @@ class IsoBar : AppCompatSeekBar { val selectedIso = isoValues[this.progress] mainActivity.isoValueText.text = selectedIso.toString() - if (mainActivity.isoButton.isSelected) { + if (mainActivity.camConfig.isManualMode) { mainActivity.camConfig.manualIsoValue = selectedIso mainActivity.camConfig.applyManualSettings() } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index 4ca8e4f1a..ce7d10d72 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -30,14 +30,14 @@ class ShutterSpeedBar : AppCompatSeekBar { if (progress < shutterValues.size) { val selectedNanos = shutterValues[progress] - // Actualizamos UI con formato legible val label = formatShutterSpeed(shutterSpeeds[progress]) mainActivity.shutterSpeedValueText.text = label mainActivity.shutterSpeedValueText.text = label - // Aplicamos a la cámara - mainActivity.camConfig.manualExposureValue = selectedNanos.toInt() // Nota: CamConfig usa Int o Long - mainActivity.camConfig.applyManualSettings() + if(mainActivity.camConfig.isManualMode){ + mainActivity.camConfig.manualExposureValue = selectedNanos.toInt() + mainActivity.camConfig.applyManualSettings() + } } } override fun onStartTrackingTouch(seekBar: SeekBar?) {} From 9af03041998b78c452bf8e1bb86d0c6775e59909 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 18:27:10 +0200 Subject: [PATCH 17/26] [FIX] Camera mode started with AE_MODE_OFF --- .../java/app/grapheneos/camera/CamConfig.kt | 7 ++----- .../camera/ui/activities/MainActivity.kt | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index fdf9bc97f..bc0957bb5 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -1047,8 +1047,7 @@ class CamConfig(private val mActivity: MainActivity) { val camera2CameraControl = Camera2CameraControl.from(cameraControl) val builder = CaptureRequestOptions.Builder() - // Lógica inteligente de AE Mode - if (manualIsoValue == null && manualExposureValue == null) { + if (!mActivity.camConfig.isManualMode) { builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON) } else { builder.setCaptureRequestOption(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF) @@ -1834,9 +1833,7 @@ class CamConfig(private val mActivity: MainActivity) { camera?.cameraInfo?.exposureState?.let { mActivity.exposureBar.setExposureConfig(it) } - if (isManualMode){ - applyManualSettings() - } + applyManualSettings() mActivity.settingsDialog.torchToggle.isChecked = false diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 8e8c7e2bf..5ea8584ba 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -233,8 +233,15 @@ open class MainActivity : AppCompatActivity(), previewView.height / 2.0f, QROverlay.RATIO ) + // In manual mode, we only want to focus (AF), not change exposure (AE) + val flags = if (camConfig.isManualMode) { + FocusMeteringAction.FLAG_AF + } else { + FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB + } + camConfig.camera?.cameraControl?.startFocusAndMetering( - FocusMeteringAction.Builder(autoFocusPoint).disableAutoCancel().build() + FocusMeteringAction.Builder(autoFocusPoint, flags).disableAutoCancel().build() ) startFocusTimer() @@ -1328,7 +1335,14 @@ open class MainActivity : AppCompatActivity(), val autoFocusPoint = previewView.meteringPointFactory.createPoint(x, y) animateFocusRing(x, y) - val focusBuilder = FocusMeteringAction.Builder(autoFocusPoint) + // In manual mode, we only want to focus (AF), not change exposure (AE) + val flags = if (camConfig.isManualMode) { + FocusMeteringAction.FLAG_AF + } else { + FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB + } + + val focusBuilder = FocusMeteringAction.Builder(autoFocusPoint, flags) if (!camConfig.isVideoMode) { camConfig.mPlayer.playFocusStartSound() From 31a71c2332fa35365039183ea296b35e831e1392 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 18:45:49 +0200 Subject: [PATCH 18/26] Disabled onTouch feature in MANUAL mode because it made flickerings --- app/src/main/java/app/grapheneos/camera/CamConfig.kt | 6 ++++++ .../app/grapheneos/camera/ui/activities/MainActivity.kt | 3 +++ 2 files changed, 9 insertions(+) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index bc0957bb5..5f5d95bbb 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -2118,6 +2118,12 @@ class CamConfig(private val mActivity: MainActivity) { manualIsoValue = null manualExposureValue = null applyManualSettings() + mActivity.startFocusTimer() + }else{ + manualIsoValue = mActivity.isoSeekBar.getCurrentIsoValue() + manualExposureValue = mActivity.shutterSpeedBar.getCurrentShutterValue().toInt() + applyManualSettings() + mActivity.cancelFocusTimer() } } diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 5ea8584ba..16356eeff 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -1329,6 +1329,9 @@ open class MainActivity : AppCompatActivity(), if (camConfig.isQRMode) return false + if(camConfig.isManualMode) + return false + val x = event.x val y = event.y From f8ec733465ddc00bfac825ca8059fdf7c88a82aa Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 18:57:17 +0200 Subject: [PATCH 19/26] Exposure value is now long --- .../java/app/grapheneos/camera/CamConfig.kt | 4 ++-- .../camera/ui/seekbar/ShutterSpeedBar.kt | 22 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 5f5d95bbb..bb68c8c8d 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -280,7 +280,7 @@ class CamConfig(private val mActivity: MainActivity) { var lastCapturedItem: CapturedItem? = null var manualIsoValue: Int? = null - var manualExposureValue: Int? = null + var manualExposureValue: Long? = null init { if (mActivity !is SecureActivity) { @@ -1055,7 +1055,7 @@ class CamConfig(private val mActivity: MainActivity) { builder.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, it) } manualExposureValue?.let { - builder.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, it.toLong()) + builder.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, it) } } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index ce7d10d72..8be26c59f 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -8,12 +8,14 @@ import app.grapheneos.camera.ui.activities.MainActivity class ShutterSpeedBar : AppCompatSeekBar { - // Valores en segundos (Double) para generar los nanosegundos reales + // Values in seconds (Double) to generate real ns private val shutterSpeeds = listOf( - 1/1000.0, 1/500.0, 1/250.0, 1/125.0, 1/60.0, 1/30.0, - 1/15.0, 1/8.0, 1/4.0, 1/2.0, 1.0, 2.0 + 1/6000.0, 1/4000.0, 1/3000.0, 1/2000.0, 1/1000.0, 1/750.0, 1/500.0, + 1/350.0, 1/250.0, 1/125.0, 1/60.0, 1/30.0, 1/25.0, 1/15.0, 1/10.0, + 1/8.0, 1/6.0, 1/4.0, 0.3, 0.5, 1.0, 2.0, 4.0, 8.0, 10.0, 15.0, 30.0 ) + private var shutterValues: List = emptyList() private lateinit var mainActivity: MainActivity @@ -48,9 +50,8 @@ class ShutterSpeedBar : AppCompatSeekBar { fun refreshShutterValues() { val range = mainActivity.camConfig.getShutterRange() val minNanos = range?.lower ?: 100_000L // 1/10000s por defecto - val maxNanos = range?.upper ?: 1_000_000_000L // 1s por defecto + val maxNanos = range?.upper ?: 1_000_000_000L // 1s default - // Convertimos nuestra lista de segundos a nanosegundos (1s = 10^9 ns) shutterValues = shutterSpeeds.map { (it * 1_000_000_000L).toLong() } .filter { it in minNanos..maxNanos } @@ -58,10 +59,13 @@ class ShutterSpeedBar : AppCompatSeekBar { } private fun formatShutterSpeed(seconds: Double): String { - return if (seconds < 1.0) { - "1/${(1.0 / seconds).toInt()}" - } else { - "${seconds.toInt()}s" + return when { + seconds >= 1.0 -> "${seconds.toInt()}s" + seconds == 0.3 || seconds == 0.5 -> "$seconds" + else -> { + val denominator = (1.0 / seconds + 0.5).toInt() + "1/$denominator" + } } } From c81fdc2515adeae8b0bd9836055d0e5b441e8d4e Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:00:11 +0200 Subject: [PATCH 20/26] Adapt ShutterSpeedBar values to hardware and software limitations --- .../java/app/grapheneos/camera/CamConfig.kt | 2 +- .../camera/ui/seekbar/ShutterSpeedBar.kt | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index bb68c8c8d..cb028a842 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -2121,7 +2121,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.startFocusTimer() }else{ manualIsoValue = mActivity.isoSeekBar.getCurrentIsoValue() - manualExposureValue = mActivity.shutterSpeedBar.getCurrentShutterValue().toInt() + manualExposureValue = mActivity.shutterSpeedBar.getCurrentShutterValue() applyManualSettings() mActivity.cancelFocusTimer() } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index 8be26c59f..b697a7c84 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -37,7 +37,7 @@ class ShutterSpeedBar : AppCompatSeekBar { mainActivity.shutterSpeedValueText.text = label if(mainActivity.camConfig.isManualMode){ - mainActivity.camConfig.manualExposureValue = selectedNanos.toInt() + mainActivity.camConfig.manualExposureValue = selectedNanos mainActivity.camConfig.applyManualSettings() } } @@ -49,13 +49,24 @@ class ShutterSpeedBar : AppCompatSeekBar { fun refreshShutterValues() { val range = mainActivity.camConfig.getShutterRange() - val minNanos = range?.lower ?: 100_000L // 1/10000s por defecto + val minNanos = range?.lower ?: 100_000L // 1/10000s default val maxNanos = range?.upper ?: 1_000_000_000L // 1s default - shutterValues = shutterSpeeds.map { (it * 1_000_000_000L).toLong() } - .filter { it in minNanos..maxNanos } + val validPairs = shutterSpeeds.map { (it * 1_000_000_000L).toLong() to it } + .filter { it.first in minNanos..maxNanos } + + shutterValues = validPairs.map { it.first } + val filteredSpeeds = validPairs.map { it.second } this.max = shutterValues.size - 1 + + if (this.progress >= shutterValues.size) { + this.progress = 0 + } + + val initialLabel = formatShutterSpeed(filteredSpeeds[this.progress]) + mainActivity.shutterSpeedValueText.text = initialLabel + mainActivity.shutterButton.text = initialLabel } private fun formatShutterSpeed(seconds: Double): String { From 2a51c1c0fdc96c98324f1ae4e45431eb437d1a2e Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:00:42 +0200 Subject: [PATCH 21/26] Remove max property from --- app/src/main/res/layout/activity_main.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 49745019d..502637c57 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -292,7 +292,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="18dp" - android:max="21" android:progress="1" android:thumbTint="@android:color/white" android:progressTint="@android:color/white" /> From c359cdf90c807510e70f583da7797671544f6236 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:07:59 +0200 Subject: [PATCH 22/26] Fix crash on launch, variables were not initialized --- .../grapheneos/camera/ui/activities/MainActivity.kt | 12 ++++++------ .../grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt | 1 - 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 16356eeff..13f66bce5 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -665,19 +665,19 @@ open class MainActivity : AppCompatActivity(), mainOverlay = binding.mainOverlay isoButton = binding.isoButton - isoSeekBar = binding.isoSeekbar - isoSeekBar.setMainActivity(this) - isoSliderContainer = binding.isoSliderContainer isoValueText = binding.isoValueText - shutterButton = binding.shutterButton + shutterSpeedValueText = binding.shutterSpeedValueText + isoSeekBar = binding.isoSeekbar shutterSpeedBar = binding.shutterSpeedSeekbar + + isoSeekBar.setMainActivity(this) shutterSpeedBar.setMainActivity(this) shutterSpeedSliderContainer = binding.shutterSpeedSliderContainer - shutterSpeedValueText = binding.shutterSpeedValueText + imageCapturer = ImageCapturer(this) @@ -867,7 +867,7 @@ open class MainActivity : AppCompatActivity(), if (it.isSelected) { shutterSpeedSliderContainer.visibility = View.VISIBLE - camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue().toInt() + camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue() } else { shutterSpeedSliderContainer.visibility = View.GONE } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index b697a7c84..9909460ee 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -66,7 +66,6 @@ class ShutterSpeedBar : AppCompatSeekBar { val initialLabel = formatShutterSpeed(filteredSpeeds[this.progress]) mainActivity.shutterSpeedValueText.text = initialLabel - mainActivity.shutterButton.text = initialLabel } private fun formatShutterSpeed(seconds: Double): String { From 4bc55d606cb19d1bb67f84e67343a48650bbcf6b Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:32:44 +0200 Subject: [PATCH 23/26] Delayed refresh shutter values so that Camera2 gets initialized first --- .../grapheneos/camera/ui/activities/MainActivity.kt | 1 + .../grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt | 11 +++++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 13f66bce5..5017cb4c9 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -709,6 +709,7 @@ open class MainActivity : AppCompatActivity(), mainOverlay.visibility = View.INVISIBLE camConfig.reloadSettings() isoSeekBar.refreshIsoValues() + shutterSpeedBar.refreshShutterValues() if (!camConfig.isQRMode) { previewGrid.visibility = View.VISIBLE if (!settingsDialog.isShowing) { diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index 9909460ee..40ec9c744 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -1,9 +1,16 @@ package app.grapheneos.camera.ui.seekbar import android.content.Context +import android.hardware.camera2.CameraCharacteristics +import android.hardware.camera2.CameraManager import android.util.AttributeSet +import android.util.Log import android.widget.SeekBar +import androidx.annotation.OptIn import androidx.appcompat.widget.AppCompatSeekBar +import androidx.camera.camera2.interop.Camera2CameraInfo +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.core.content.ContextCompat.getSystemService import app.grapheneos.camera.ui.activities.MainActivity class ShutterSpeedBar : AppCompatSeekBar { @@ -25,7 +32,6 @@ class ShutterSpeedBar : AppCompatSeekBar { fun setMainActivity(mainActivity: MainActivity) { this.mainActivity = mainActivity - refreshShutterValues() this.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { @@ -47,12 +53,13 @@ class ShutterSpeedBar : AppCompatSeekBar { }) } + @OptIn(ExperimentalCamera2Interop::class) fun refreshShutterValues() { val range = mainActivity.camConfig.getShutterRange() val minNanos = range?.lower ?: 100_000L // 1/10000s default val maxNanos = range?.upper ?: 1_000_000_000L // 1s default - val validPairs = shutterSpeeds.map { (it * 1_000_000_000L).toLong() to it } + val validPairs = shutterSpeeds.map { (it * 1_000_000_000.0).toLong() to it } .filter { it.first in minNanos..maxNanos } shutterValues = validPairs.map { it.first } From 16185ce3a27c4a2d8e231390d6d7fb95fd5e39f4 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:45:31 +0200 Subject: [PATCH 24/26] Standarize, rename variables, refactor to improve mantainability --- .../main/java/app/grapheneos/camera/CamConfig.kt | 14 +++++++++----- .../camera/ui/activities/MainActivity.kt | 3 +-- .../camera/ui/seekbar/ShutterSpeedBar.kt | 14 +++++--------- app/src/main/res/layout/activity_main.xml | 4 ++-- app/src/main/res/values/strings.xml | 7 +++++-- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index cb028a842..5c1a360be 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -280,7 +280,7 @@ class CamConfig(private val mActivity: MainActivity) { var lastCapturedItem: CapturedItem? = null var manualIsoValue: Int? = null - var manualExposureValue: Long? = null + var manualExposureTimeValue: Long? = null init { if (mActivity !is SecureActivity) { @@ -1041,6 +1041,10 @@ class CamConfig(private val mActivity: MainActivity) { .getCameraCharacteristic(CameraCharacteristics.SENSOR_INFO_EXPOSURE_TIME_RANGE) } + /** + * Applies manual ISO and Exposure Time settings to the camera hardware. + * If camera is not in manual mode, it restores Auto Exposure (AE) mode. + */ @androidx.annotation.OptIn(androidx.camera.camera2.interop.ExperimentalCamera2Interop::class) fun applyManualSettings() { val cameraControl = camera?.cameraControl ?: return @@ -1054,7 +1058,7 @@ class CamConfig(private val mActivity: MainActivity) { manualIsoValue?.let { builder.setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, it) } - manualExposureValue?.let { + manualExposureTimeValue?.let { builder.setCaptureRequestOption(CaptureRequest.SENSOR_EXPOSURE_TIME, it) } } @@ -2092,7 +2096,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.isoSliderContainer.visibility = View.GONE //mActivity.updateIsoButtonUI() manualIsoValue = null - manualExposureValue = null + manualExposureTimeValue = null applyManualSettings() } else { mActivity.qrOverlay.visibility = View.INVISIBLE @@ -2116,12 +2120,12 @@ class CamConfig(private val mActivity: MainActivity) { if (!isManualMode) { mActivity.deselectAllManualControls() manualIsoValue = null - manualExposureValue = null + manualExposureTimeValue = null applyManualSettings() mActivity.startFocusTimer() }else{ manualIsoValue = mActivity.isoSeekBar.getCurrentIsoValue() - manualExposureValue = mActivity.shutterSpeedBar.getCurrentShutterValue() + manualExposureTimeValue = mActivity.shutterSpeedBar.getCurrentShutterValue() applyManualSettings() mActivity.cancelFocusTimer() } diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index 5017cb4c9..bce4a0dcc 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -44,7 +44,6 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.ProgressBar import android.widget.RelativeLayout -import android.widget.SeekBar import android.widget.TextView import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -868,7 +867,7 @@ open class MainActivity : AppCompatActivity(), if (it.isSelected) { shutterSpeedSliderContainer.visibility = View.VISIBLE - camConfig.manualExposureValue = shutterSpeedBar.getCurrentShutterValue() + camConfig.manualExposureTimeValue = shutterSpeedBar.getCurrentShutterValue() } else { shutterSpeedSliderContainer.visibility = View.GONE } diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt index 40ec9c744..b3b8065b8 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt @@ -1,16 +1,12 @@ package app.grapheneos.camera.ui.seekbar import android.content.Context -import android.hardware.camera2.CameraCharacteristics -import android.hardware.camera2.CameraManager import android.util.AttributeSet -import android.util.Log import android.widget.SeekBar import androidx.annotation.OptIn import androidx.appcompat.widget.AppCompatSeekBar -import androidx.camera.camera2.interop.Camera2CameraInfo import androidx.camera.camera2.interop.ExperimentalCamera2Interop -import androidx.core.content.ContextCompat.getSystemService +import app.grapheneos.camera.R import app.grapheneos.camera.ui.activities.MainActivity class ShutterSpeedBar : AppCompatSeekBar { @@ -43,7 +39,7 @@ class ShutterSpeedBar : AppCompatSeekBar { mainActivity.shutterSpeedValueText.text = label if(mainActivity.camConfig.isManualMode){ - mainActivity.camConfig.manualExposureValue = selectedNanos + mainActivity.camConfig.manualExposureTimeValue = selectedNanos mainActivity.camConfig.applyManualSettings() } } @@ -77,11 +73,11 @@ class ShutterSpeedBar : AppCompatSeekBar { private fun formatShutterSpeed(seconds: Double): String { return when { - seconds >= 1.0 -> "${seconds.toInt()}s" - seconds == 0.3 || seconds == 0.5 -> "$seconds" + seconds >= 1.0 -> context.getString(R.string.shutter_speed_seconds, seconds.toInt()) + seconds == 0.3 || seconds == 0.5 -> context.getString(R.string.shutter_speed_decimal, seconds) else -> { val denominator = (1.0 / seconds + 0.5).toInt() - "1/$denominator" + context.getString(R.string.shutter_speed_fraction, denominator) } } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 502637c57..4abbdeb40 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -309,7 +309,7 @@ android:id="@+id/iso_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/iso" + android:text="@string/iso_button_label" android:textColor="@android:color/white" android:textSize="14sp" android:textStyle="bold" @@ -329,7 +329,7 @@ android:id="@+id/shutter_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/shutter_speed" + android:text="@string/shutter_speed_button_label" android:textColor="@android:color/white" android:textSize="14sp" android:textStyle="bold" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cf189b55a..bc1e5215f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -87,10 +87,13 @@ More Options PDF 417 More Settings - ISO + ISO 100 - Shutter speed + Shutter speed 100 + 1/%1$d + %1$ds + %1$.1f General From 22da6003f63669c8ab1a797dd2d0626839b32be6 Mon Sep 17 00:00:00 2001 From: alexceend Date: Tue, 4 Aug 2026 19:55:24 +0200 Subject: [PATCH 25/26] Rename shutter speed to Exposure time because its more accurate --- .../java/app/grapheneos/camera/CamConfig.kt | 4 +- .../camera/ui/activities/MainActivity.kt | 34 +++++++-------- ...{ShutterSpeedBar.kt => ExposureTimeBar.kt} | 42 +++++++++---------- app/src/main/res/layout/activity_main.xml | 14 +++---- app/src/main/res/values/strings.xml | 10 ++--- 5 files changed, 52 insertions(+), 52 deletions(-) rename app/src/main/java/app/grapheneos/camera/ui/seekbar/{ShutterSpeedBar.kt => ExposureTimeBar.kt} (63%) diff --git a/app/src/main/java/app/grapheneos/camera/CamConfig.kt b/app/src/main/java/app/grapheneos/camera/CamConfig.kt index 5c1a360be..085f8352f 100644 --- a/app/src/main/java/app/grapheneos/camera/CamConfig.kt +++ b/app/src/main/java/app/grapheneos/camera/CamConfig.kt @@ -2116,7 +2116,7 @@ class CamConfig(private val mActivity: MainActivity) { } mActivity.isoButton.visibility = if (isManualMode) View.VISIBLE else View.GONE - mActivity.shutterButton.visibility = if (isManualMode) View.VISIBLE else View.GONE + mActivity.exposureTimeButton.visibility = if (isManualMode) View.VISIBLE else View.GONE if (!isManualMode) { mActivity.deselectAllManualControls() manualIsoValue = null @@ -2125,7 +2125,7 @@ class CamConfig(private val mActivity: MainActivity) { mActivity.startFocusTimer() }else{ manualIsoValue = mActivity.isoSeekBar.getCurrentIsoValue() - manualExposureTimeValue = mActivity.shutterSpeedBar.getCurrentShutterValue() + manualExposureTimeValue = mActivity.exposureTimeBar.getCurrentExposureTimeValue() applyManualSettings() mActivity.cancelFocusTimer() } diff --git a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt index bce4a0dcc..910451eb8 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/activities/MainActivity.kt @@ -89,7 +89,7 @@ import app.grapheneos.camera.ui.QRToggle import app.grapheneos.camera.ui.SettingsDialog import app.grapheneos.camera.ui.seekbar.ExposureBar import app.grapheneos.camera.ui.seekbar.IsoBar -import app.grapheneos.camera.ui.seekbar.ShutterSpeedBar +import app.grapheneos.camera.ui.seekbar.ExposureTimeBar import app.grapheneos.camera.ui.seekbar.ZoomBar import app.grapheneos.camera.ui.showIgnoringShortEdgeMode import app.grapheneos.camera.util.CameraControl @@ -264,21 +264,21 @@ open class MainActivity : AppCompatActivity(), lateinit var isoSliderContainer: View lateinit var isoValueText: TextView - lateinit var shutterButton: TextView - lateinit var shutterSpeedBar: ShutterSpeedBar - lateinit var shutterSpeedSliderContainer: View - lateinit var shutterSpeedValueText: TextView + lateinit var exposureTimeButton: TextView + lateinit var exposureTimeBar: ExposureTimeBar + lateinit var exposureTimeSliderContainer: View + lateinit var exposureTimeValueText: TextView private val manualControlPairs by lazy { listOf( isoButton to isoSliderContainer, - shutterButton to shutterSpeedSliderContainer + exposureTimeButton to exposureTimeSliderContainer ) } fun updateManualButtonsUI(){ - val buttons : List = listOf(isoButton, shutterButton) + val buttons : List = listOf(isoButton, exposureTimeButton) for(b in buttons){ updateManualButtonUI(b) } @@ -666,16 +666,16 @@ open class MainActivity : AppCompatActivity(), isoSliderContainer = binding.isoSliderContainer isoValueText = binding.isoValueText - shutterButton = binding.shutterButton - shutterSpeedValueText = binding.shutterSpeedValueText + exposureTimeButton = binding.exposureTimeButton + exposureTimeValueText = binding.exposureTimeValueText isoSeekBar = binding.isoSeekbar - shutterSpeedBar = binding.shutterSpeedSeekbar + exposureTimeBar = binding.exposureTimeSeekbar isoSeekBar.setMainActivity(this) - shutterSpeedBar.setMainActivity(this) + exposureTimeBar.setMainActivity(this) - shutterSpeedSliderContainer = binding.shutterSpeedSliderContainer + exposureTimeSliderContainer = binding.exposureTimeSliderContainer @@ -708,7 +708,7 @@ open class MainActivity : AppCompatActivity(), mainOverlay.visibility = View.INVISIBLE camConfig.reloadSettings() isoSeekBar.refreshIsoValues() - shutterSpeedBar.refreshShutterValues() + exposureTimeBar.refreshExposureTimeValues() if (!camConfig.isQRMode) { previewGrid.visibility = View.VISIBLE if (!settingsDialog.isShowing) { @@ -860,16 +860,16 @@ open class MainActivity : AppCompatActivity(), updateManualButtonsUI() } - shutterButton.setOnClickListener { + exposureTimeButton.setOnClickListener { val targetState = !it.isSelected deselectAllManualControls() it.isSelected = targetState if (it.isSelected) { - shutterSpeedSliderContainer.visibility = View.VISIBLE - camConfig.manualExposureTimeValue = shutterSpeedBar.getCurrentShutterValue() + exposureTimeSliderContainer.visibility = View.VISIBLE + camConfig.manualExposureTimeValue = exposureTimeBar.getCurrentExposureTimeValue() } else { - shutterSpeedSliderContainer.visibility = View.GONE + exposureTimeSliderContainer.visibility = View.GONE } camConfig.applyManualSettings() updateManualButtonsUI() diff --git a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ExposureTimeBar.kt similarity index 63% rename from app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt rename to app/src/main/java/app/grapheneos/camera/ui/seekbar/ExposureTimeBar.kt index b3b8065b8..dac7e4e6b 100644 --- a/app/src/main/java/app/grapheneos/camera/ui/seekbar/ShutterSpeedBar.kt +++ b/app/src/main/java/app/grapheneos/camera/ui/seekbar/ExposureTimeBar.kt @@ -9,17 +9,17 @@ import androidx.camera.camera2.interop.ExperimentalCamera2Interop import app.grapheneos.camera.R import app.grapheneos.camera.ui.activities.MainActivity -class ShutterSpeedBar : AppCompatSeekBar { +class ExposureTimeBar : AppCompatSeekBar { // Values in seconds (Double) to generate real ns - private val shutterSpeeds = listOf( + private val exposureTimes = listOf( 1/6000.0, 1/4000.0, 1/3000.0, 1/2000.0, 1/1000.0, 1/750.0, 1/500.0, 1/350.0, 1/250.0, 1/125.0, 1/60.0, 1/30.0, 1/25.0, 1/15.0, 1/10.0, 1/8.0, 1/6.0, 1/4.0, 0.3, 0.5, 1.0, 2.0, 4.0, 8.0, 10.0, 15.0, 30.0 ) - private var shutterValues: List = emptyList() + private var exposureTimeValues: List = emptyList() private lateinit var mainActivity: MainActivity constructor(context: Context) : super(context) @@ -31,12 +31,12 @@ class ShutterSpeedBar : AppCompatSeekBar { this.setOnSeekBarChangeListener(object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { - if (progress < shutterValues.size) { - val selectedNanos = shutterValues[progress] + if (progress < exposureTimeValues.size) { + val selectedNanos = exposureTimeValues[progress] - val label = formatShutterSpeed(shutterSpeeds[progress]) - mainActivity.shutterSpeedValueText.text = label - mainActivity.shutterSpeedValueText.text = label + val label = formatExposureValuesSpeed(exposureTimes[progress]) + mainActivity.exposureTimeValueText.text = label + mainActivity.exposureTimeValueText.text = label if(mainActivity.camConfig.isManualMode){ mainActivity.camConfig.manualExposureTimeValue = selectedNanos @@ -50,39 +50,39 @@ class ShutterSpeedBar : AppCompatSeekBar { } @OptIn(ExperimentalCamera2Interop::class) - fun refreshShutterValues() { + fun refreshExposureTimeValues() { val range = mainActivity.camConfig.getShutterRange() val minNanos = range?.lower ?: 100_000L // 1/10000s default val maxNanos = range?.upper ?: 1_000_000_000L // 1s default - val validPairs = shutterSpeeds.map { (it * 1_000_000_000.0).toLong() to it } + val validPairs = exposureTimes.map { (it * 1_000_000_000.0).toLong() to it } .filter { it.first in minNanos..maxNanos } - shutterValues = validPairs.map { it.first } + exposureTimeValues = validPairs.map { it.first } val filteredSpeeds = validPairs.map { it.second } - this.max = shutterValues.size - 1 + this.max = exposureTimeValues.size - 1 - if (this.progress >= shutterValues.size) { + if (this.progress >= exposureTimeValues.size) { this.progress = 0 } - val initialLabel = formatShutterSpeed(filteredSpeeds[this.progress]) - mainActivity.shutterSpeedValueText.text = initialLabel + val initialLabel = formatExposureValuesSpeed(filteredSpeeds[this.progress]) + mainActivity.exposureTimeValueText.text = initialLabel } - private fun formatShutterSpeed(seconds: Double): String { + private fun formatExposureValuesSpeed(seconds: Double): String { return when { - seconds >= 1.0 -> context.getString(R.string.shutter_speed_seconds, seconds.toInt()) - seconds == 0.3 || seconds == 0.5 -> context.getString(R.string.shutter_speed_decimal, seconds) + seconds >= 1.0 -> context.getString(R.string.exposure_time_seconds, seconds.toInt()) + seconds == 0.3 || seconds == 0.5 -> context.getString(R.string.exposure_time_decimal, seconds) else -> { val denominator = (1.0 / seconds + 0.5).toInt() - context.getString(R.string.shutter_speed_fraction, denominator) + context.getString(R.string.exposure_time_fraction, denominator) } } } - fun getCurrentShutterValue(): Long { - return if (progress < shutterValues.size) shutterValues[progress] else shutterValues.last() + fun getCurrentExposureTimeValue(): Long { + return if (progress < exposureTimeValues.size) exposureTimeValues[progress] else exposureTimeValues.last() } } \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 4abbdeb40..6913c804e 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -257,7 +257,7 @@ - More Settings ISO 100 - Shutter speed - 100 - 1/%1$d - %1$ds - %1$.1f + Exposure Time + 100 + 1/%1$d + %1$ds + %1$.1f General From 35c984215252034171f395090dced9b80c3e364e Mon Sep 17 00:00:00 2001 From: alexceend Date: Wed, 5 Aug 2026 01:46:59 +0200 Subject: [PATCH 26/26] Remove IDE-specific changes --- .idea/deviceManager.xml | 13 ------------- .idea/misc.xml | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 .idea/deviceManager.xml diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml deleted file mode 100644 index 91f95584d..000000000 --- a/.idea/deviceManager.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d2f3f09f4..84db41232 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -120,7 +120,7 @@ - +