From 2248abb749c223e1139c437f9e2b38af94c8e775 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Fri, 6 Feb 2026 15:10:17 -0500 Subject: [PATCH] Persist camera lens selection across sessions. --- .../securesms/keyvalue/MiscellaneousValues.kt | 6 ++++++ .../securesms/mediasend/Camera1Fragment.java | 6 +++--- .../securesms/mediasend/CameraXFragment.kt | 20 ++++++++++++++++++- .../securesms/util/TextSecurePreferences.java | 11 ---------- demo/camera/build.gradle.kts | 2 +- .../signal/camera/CameraScreenViewModel.kt | 4 ++++ 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt index c61b85662d..1cb3d31484 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/keyvalue/MiscellaneousValues.kt @@ -46,6 +46,7 @@ class MiscellaneousValues internal constructor(store: KeyValueStore) : SignalSto private const val LAST_KEY_TRANSPARENCY_TIME = "misc.last_key_transparency_time" private const val HAS_KEY_TRANSPARENCY_FAILURE = "misc.has_key_transparency_failure" private const val HAS_SEEN_KEY_TRANSPARENCY_FAILURE = "misc.has_seen_key_transparency_failure" + private const val CAMERA_FACING_FRONT = "misc.camera_facing_front" } public override fun onFirstEverAppLaunch() { @@ -309,4 +310,9 @@ class MiscellaneousValues internal constructor(store: KeyValueStore) : SignalSto * Whether you have seen the dialog on key transparency failure */ var hasSeenKeyTransparencyFailure: Boolean by booleanValue(HAS_SEEN_KEY_TRANSPARENCY_FAILURE, false) + + /** + * Whether or not the preferred camera direction is front-facing. + */ + var isCameraFacingFront: Boolean by booleanValue(CAMERA_FACING_FRONT, true) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java index 494751eef5..e679c9e83f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java @@ -51,7 +51,7 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations; import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton; import org.signal.glide.decryptableuri.DecryptableUri; import org.thoughtcrime.securesms.util.ServiceUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.util.ViewUtil; import java.io.ByteArrayOutputStream; @@ -107,7 +107,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, display.getSize(displaySize); - camera = new Camera1Controller(TextSecurePreferences.getDirectCaptureCameraId(getContext()), displaySize.x, displaySize.y, this); + camera = new Camera1Controller(SignalStore.misc().isCameraFacingFront() ? Camera.CameraInfo.CAMERA_FACING_FRONT : Camera.CameraInfo.CAMERA_FACING_BACK, displaySize.x, displaySize.y, this); orderEnforcer = new OrderEnforcer<>(Stage.SURFACE_AVAILABLE, Stage.CAMERA_PROPERTIES_AVAILABLE); } @@ -347,7 +347,7 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, flipButton.setVisibility(properties.getCameraCount() > 1 ? View.VISIBLE : View.GONE); flipButton.setOnClickListener(v -> { int newCameraId = camera.flip(); - TextSecurePreferences.setDirectCaptureCameraId(getContext(), newCameraId); + SignalStore.misc().setCameraFacingFront(newCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT); Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); animation.setDuration(200); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt index b9102e9c13..219f78358d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.kt @@ -8,6 +8,7 @@ import android.os.Build import android.os.Bundle import android.os.ParcelFileDescriptor import android.widget.Toast +import androidx.camera.core.CameraSelector import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn @@ -29,6 +30,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -50,6 +52,7 @@ import org.signal.core.ui.BottomSheetUtil import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.compose.ComposeFragment +import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.mediasend.camerax.CameraXModePolicy import org.thoughtcrime.securesms.permissions.PermissionDeniedBottomSheet.Companion.showPermissionFragment import org.thoughtcrime.securesms.permissions.Permissions @@ -101,7 +104,6 @@ class CameraXFragment : ComposeFragment(), CameraFragment { private val isQrScanEnabled: Boolean get() = requireArguments().getBoolean(IS_QR_SCAN_ENABLED, false) - // Compose state holders for HUD visibility private var controlsVisible = mutableStateOf(true) private var selectedMediaCount = mutableIntStateOf(0) @@ -295,6 +297,22 @@ private fun CameraXScreen( val cameraState by cameraViewModel.state var hasPermission by remember { mutableStateOf(hasCameraPermission()) } + LaunchedEffect(cameraViewModel) { + val lensFacing = if (SignalStore.misc.isCameraFacingFront) { + CameraSelector.LENS_FACING_FRONT + } else { + CameraSelector.LENS_FACING_BACK + } + cameraViewModel.setLensFacing(lensFacing) + } + + LaunchedEffect(cameraViewModel) { + snapshotFlow { cameraState.lensFacing } + .collect { lensFacing -> + SignalStore.misc.isCameraFacingFront = lensFacing == CameraSelector.LENS_FACING_FRONT + } + } + LaunchedEffect(Unit) { if (!hasPermission) { onCheckPermissions() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 1c289dff2f..24c2e1b40f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -5,7 +5,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; -import android.hardware.Camera.CameraInfo; import android.net.Uri; import android.provider.Settings; @@ -88,7 +87,6 @@ public class TextSecurePreferences { public static final String SYSTEM_EMOJI_PREF = "pref_system_emoji"; private static final String MULTI_DEVICE_PROVISIONED_PREF = "pref_multi_device"; - public static final String DIRECT_CAPTURE_CAMERA_ID = "pref_direct_capture_camera_id"; public static final String ALWAYS_RELAY_CALLS_PREF = "pref_turn_only"; public static final String READ_RECEIPTS_PREF = "pref_read_receipts"; public static final String INCOGNITO_KEYBOARD_PREF = "pref_incognito_keyboard"; @@ -445,15 +443,6 @@ public class TextSecurePreferences { return getBooleanPreference(context, ALWAYS_RELAY_CALLS_PREF, false); } - public static void setDirectCaptureCameraId(Context context, int value) { - setIntegerPrefrence(context, DIRECT_CAPTURE_CAMERA_ID, value); - } - - @SuppressWarnings("deprecation") - public static int getDirectCaptureCameraId(Context context) { - return getIntegerPreference(context, DIRECT_CAPTURE_CAMERA_ID, CameraInfo.CAMERA_FACING_FRONT); - } - @Deprecated public static NotificationPrivacyPreference getNotificationPrivacy(Context context) { return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); diff --git a/demo/camera/build.gradle.kts b/demo/camera/build.gradle.kts index 96620aec66..633c30ac27 100644 --- a/demo/camera/build.gradle.kts +++ b/demo/camera/build.gradle.kts @@ -51,7 +51,7 @@ android { dependencies { // Camera feature module implementation(project(":feature:camera")) - + // Core modules implementation(project(":core:ui")) diff --git a/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt b/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt index d482e3cd8e..40eb2170cc 100644 --- a/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt +++ b/feature/camera/src/main/java/org/signal/camera/CameraScreenViewModel.kt @@ -398,6 +398,10 @@ class CameraScreenViewModel : ViewModel() { _state.value = state.copy(zoomRatio = newZoom) } + fun setLensFacing(lensFacing: Int) { + _state.value = _state.value.copy(lensFacing = lensFacing) + } + private fun handleSwitchCameraEvent(state: CameraScreenState) { if (state.isRecording) { return