Move CameraXFragment to feature:media-send.
This commit is contained in:
parent
4e077bbb52
commit
ab4a38d565
@ -79,6 +79,7 @@ import org.thoughtcrime.securesms.providers.BlobProvider
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.util.UUID
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
@ -158,8 +159,8 @@ class UsernameLinkSettingsFragment : ComposeFragment() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.withPermanentDenialDialog(getString(MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, MediaSendR.string.CameraXFragment_allow_access_camera, MediaSendR.string.CameraXFragment_to_scan_qr_codes, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ import org.signal.core.ui.compose.theme.SignalTheme
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.qr.QrCrosshair
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
/**
|
||||
* A screen that allows you to scan a QR code to start a chat.
|
||||
@ -116,7 +117,7 @@ fun UsernameQrScanScreen(
|
||||
.padding(48.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.CameraXFragment_to_scan_qr_code_allow_camera),
|
||||
text = stringResource(MediaSendR.string.CameraXFragment_to_scan_qr_code_allow_camera),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color.White
|
||||
@ -125,7 +126,7 @@ fun UsernameQrScanScreen(
|
||||
colors = ButtonDefaults.filledTonalButtonColors(),
|
||||
onClick = onOpenCameraClicked
|
||||
) {
|
||||
Text(stringResource(R.string.CameraXFragment_allow_access))
|
||||
Text(stringResource(MediaSendR.string.CameraXFragment_allow_access))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -47,6 +47,7 @@ import org.signal.core.util.permissions.PermissionCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
/**
|
||||
* Prompts the user to scan a username QR code. Uses the activity result to communicate the recipient that was found, or null if no valid usernames were scanned.
|
||||
@ -132,8 +133,8 @@ class UsernameQrScannerActivity : AppCompatActivity() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, supportFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(this, R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.withPermanentDenialDialog(getString(MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, MediaSendR.string.CameraXFragment_allow_access_camera, MediaSendR.string.CameraXFragment_to_scan_qr_codes, supportFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(this, MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import android.app.Application
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import io.reactivex.rxjava3.subjects.BehaviorSubject
|
||||
import okhttp3.OkHttpClient
|
||||
import org.signal.camera.CameraDependencies
|
||||
import org.signal.core.ui.CoreUiDependencies
|
||||
import org.signal.core.util.CoreUtilDependencies
|
||||
import org.signal.core.util.billing.BillingApi
|
||||
@ -118,6 +119,7 @@ object AppDependencies {
|
||||
)
|
||||
CoreUiDependencies.init(application, CoreUiDependenciesProvider)
|
||||
SignalGlideDependencies.init(application, SignalGlideDependenciesProvider)
|
||||
CameraDependencies.init(application, CameraDependenciesProvider)
|
||||
MediaSendDependencies.init(application, MediaSendDependenciesProvider)
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.dependencies
|
||||
|
||||
import org.signal.camera.CameraDependencies
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
|
||||
object CameraDependenciesProvider : CameraDependencies.Provider {
|
||||
override fun isStoriesFeatureEnabled(): Boolean {
|
||||
return Stories.isFeatureEnabled()
|
||||
}
|
||||
}
|
||||
@ -38,6 +38,7 @@ import org.signal.core.ui.permissions.Permissions
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.util.VibrateUtil
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
/**
|
||||
* Fragment that allows users to scan a QR code from their camera to link a device
|
||||
@ -102,8 +103,8 @@ class AddLinkDeviceFragment : ComposeFragment() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withPermanentDenialDialog(getString(R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.withPermanentDenialDialog(getString(MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code), null, MediaSendR.string.CameraXFragment_allow_access_camera, MediaSendR.string.CameraXFragment_to_scan_qr_codes, parentFragmentManager)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.linkdevice.LinkDeviceRepository.LinkDeviceResult
|
||||
import org.thoughtcrime.securesms.qr.QrCrosshair
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
/**
|
||||
* A screen that allows you to scan a QR code to link a device
|
||||
@ -141,7 +142,7 @@ fun LinkDeviceQrScanScreen(
|
||||
.padding(48.dp)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.CameraXFragment_to_scan_qr_code_allow_camera),
|
||||
text = stringResource(MediaSendR.string.CameraXFragment_to_scan_qr_code_allow_camera),
|
||||
textAlign = TextAlign.Center,
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
color = Color.White
|
||||
@ -150,7 +151,7 @@ fun LinkDeviceQrScanScreen(
|
||||
colors = ButtonDefaults.filledTonalButtonColors(),
|
||||
onClick = onRequestPermissions
|
||||
) {
|
||||
Text(stringResource(R.string.CameraXFragment_allow_access))
|
||||
Text(stringResource(MediaSendR.string.CameraXFragment_allow_access))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.util.BucketInfo
|
||||
import org.thoughtcrime.securesms.util.DeviceProperties
|
||||
import org.thoughtcrime.securesms.util.WakeLockUtil
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
/**
|
||||
|
||||
@ -17,7 +17,8 @@ import androidx.fragment.app.FragmentTransaction;
|
||||
import org.signal.core.models.media.Media;
|
||||
import org.signal.imageeditor.core.model.EditorModel;
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.signal.mediasend.capture.CameraFragment;
|
||||
import org.signal.mediasend.CameraFragment;
|
||||
import org.signal.mediasend.capture.CameraXFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mediasend.v2.gallery.MediaGalleryFragment;
|
||||
import org.thoughtcrime.securesms.mms.PushMediaConstraints;
|
||||
|
||||
@ -1,246 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mediasend;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.core.util.DimensionUnit;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.signal.core.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class CameraButtonView extends View {
|
||||
|
||||
private enum CameraButtonMode { IMAGE, MIXED }
|
||||
|
||||
private static final float CAPTURE_ARC_STROKE_WIDTH = 3.5f;
|
||||
private static final int CAPTURE_FILL_PROTECTION = 10;
|
||||
private static final int PROGRESS_ARC_STROKE_WIDTH = 4;
|
||||
private static final int HALF_PROGRESS_ARC_STROKE_WIDTH = PROGRESS_ARC_STROKE_WIDTH / 2;
|
||||
private static final float DEADZONE_REDUCTION_PERCENT = 0.35f;
|
||||
|
||||
private final @NonNull Paint outlinePaint = outlinePaint();
|
||||
private final @NonNull Paint backgroundPaint = backgroundPaint();
|
||||
private final @NonNull Paint arcPaint = arcPaint();
|
||||
private final @NonNull Paint recordPaint = recordPaint();
|
||||
private final @NonNull Paint progressPaint = progressPaint();
|
||||
private final @NonNull Paint captureFillPaint = captureFillPaint();
|
||||
|
||||
private Animation growAnimation;
|
||||
private Animation shrinkAnimation;
|
||||
|
||||
private boolean isRecordingVideo;
|
||||
private float progressPercent = 0f;
|
||||
|
||||
private @NonNull CameraButtonMode cameraButtonMode = CameraButtonMode.IMAGE;
|
||||
|
||||
private final float imageCaptureSize;
|
||||
private final float recordSize;
|
||||
private final RectF progressRect = new RectF();
|
||||
private final Rect deadzoneRect = new Rect();
|
||||
|
||||
|
||||
public CameraButtonView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public CameraButtonView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CameraButtonView, defStyleAttr, 0);
|
||||
|
||||
imageCaptureSize = a.getDimensionPixelSize(R.styleable.CameraButtonView_imageCaptureSize, -1);
|
||||
recordSize = a.getDimensionPixelSize(R.styleable.CameraButtonView_recordSize, -1);
|
||||
a.recycle();
|
||||
|
||||
initializeImageAnimations();
|
||||
}
|
||||
|
||||
private static Paint recordPaint() {
|
||||
Paint recordPaint = new Paint();
|
||||
recordPaint.setColor(0xFFF44336);
|
||||
recordPaint.setAntiAlias(true);
|
||||
recordPaint.setStyle(Paint.Style.FILL);
|
||||
return recordPaint;
|
||||
}
|
||||
|
||||
private static Paint outlinePaint() {
|
||||
Paint outlinePaint = new Paint();
|
||||
outlinePaint.setColor(0x26000000);
|
||||
outlinePaint.setAntiAlias(true);
|
||||
outlinePaint.setStyle(Paint.Style.STROKE);
|
||||
outlinePaint.setStrokeWidth(ViewUtil.dpToPx(4));
|
||||
return outlinePaint;
|
||||
}
|
||||
|
||||
private static Paint backgroundPaint() {
|
||||
Paint backgroundPaint = new Paint();
|
||||
backgroundPaint.setColor(0x4CFFFFFF);
|
||||
backgroundPaint.setAntiAlias(true);
|
||||
backgroundPaint.setStyle(Paint.Style.FILL);
|
||||
return backgroundPaint;
|
||||
}
|
||||
|
||||
private static Paint arcPaint() {
|
||||
Paint arcPaint = new Paint();
|
||||
arcPaint.setColor(0xFFFFFFFF);
|
||||
arcPaint.setAntiAlias(true);
|
||||
arcPaint.setStyle(Paint.Style.STROKE);
|
||||
arcPaint.setStrokeWidth(DimensionUnit.DP.toPixels(CAPTURE_ARC_STROKE_WIDTH));
|
||||
return arcPaint;
|
||||
}
|
||||
|
||||
private static Paint captureFillPaint() {
|
||||
Paint arcPaint = new Paint();
|
||||
arcPaint.setColor(0xFFFFFFFF);
|
||||
arcPaint.setAntiAlias(true);
|
||||
arcPaint.setStyle(Paint.Style.FILL);
|
||||
return arcPaint;
|
||||
}
|
||||
|
||||
|
||||
private static Paint progressPaint() {
|
||||
Paint progressPaint = new Paint();
|
||||
progressPaint.setColor(0xFFFFFFFF);
|
||||
progressPaint.setAntiAlias(true);
|
||||
progressPaint.setStyle(Paint.Style.STROKE);
|
||||
progressPaint.setStrokeWidth(ViewUtil.dpToPx(PROGRESS_ARC_STROKE_WIDTH));
|
||||
progressPaint.setShadowLayer(4, 0, 2, 0x40000000);
|
||||
return progressPaint;
|
||||
}
|
||||
|
||||
private void initializeImageAnimations() {
|
||||
shrinkAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_shrink);
|
||||
growAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.camera_capture_button_grow);
|
||||
|
||||
shrinkAnimation.setFillAfter(true);
|
||||
shrinkAnimation.setFillEnabled(true);
|
||||
growAnimation.setFillAfter(true);
|
||||
growAnimation.setFillEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
super.onDraw(canvas);
|
||||
|
||||
if (isRecordingVideo) {
|
||||
drawForVideoCapture(canvas);
|
||||
} else {
|
||||
drawForImageCapture(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawForImageCapture(Canvas canvas) {
|
||||
float centerX = getWidth() / 2f;
|
||||
float centerY = getHeight() / 2f;
|
||||
|
||||
float radius = imageCaptureSize / 2f;
|
||||
canvas.drawCircle(centerX, centerY, radius, backgroundPaint);
|
||||
canvas.drawCircle(centerX, centerY, radius, arcPaint);
|
||||
canvas.drawCircle(centerX, centerY, radius - DimensionUnit.DP.toPixels(CAPTURE_FILL_PROTECTION), captureFillPaint);
|
||||
}
|
||||
|
||||
private void drawForVideoCapture(Canvas canvas) {
|
||||
float centerX = getWidth() / 2f;
|
||||
float centerY = getHeight() / 2f;
|
||||
|
||||
canvas.drawCircle(centerX, centerY, centerY, backgroundPaint);
|
||||
canvas.drawCircle(centerX, centerY, centerY, outlinePaint);
|
||||
|
||||
canvas.drawCircle(centerX, centerY, recordSize / 2f, recordPaint);
|
||||
|
||||
progressRect.top = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
|
||||
progressRect.left = ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
|
||||
progressRect.right = getWidth() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
|
||||
progressRect.bottom = getHeight() - ViewUtil.dpToPx(HALF_PROGRESS_ARC_STROKE_WIDTH);
|
||||
|
||||
canvas.drawArc(progressRect, 270f, 360f * progressPercent, false, progressPaint);
|
||||
}
|
||||
|
||||
public void setProgress(float percentage) {
|
||||
progressPercent = Util.clamp(percentage, 0f, 1f);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (cameraButtonMode == CameraButtonMode.IMAGE) {
|
||||
return handleImageModeTouchEvent(event);
|
||||
}
|
||||
|
||||
boolean eventWasHandled = handleVideoModeTouchEvent(event);
|
||||
int action = event.getAction();
|
||||
|
||||
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
|
||||
isRecordingVideo = false;
|
||||
}
|
||||
|
||||
return eventWasHandled;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
|
||||
getLocalVisibleRect(deadzoneRect);
|
||||
deadzoneRect.left += (int) (getWidth() * DEADZONE_REDUCTION_PERCENT / 2f);
|
||||
deadzoneRect.top += (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
|
||||
deadzoneRect.right -= (int) (getWidth() * DEADZONE_REDUCTION_PERCENT / 2f);
|
||||
deadzoneRect.bottom -= (int) (getHeight() * DEADZONE_REDUCTION_PERCENT / 2f);
|
||||
}
|
||||
|
||||
private boolean handleImageModeTouchEvent(MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (isEnabled()) {
|
||||
startAnimation(shrinkAnimation);
|
||||
performClick();
|
||||
}
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
startAnimation(growAnimation);
|
||||
return true;
|
||||
default:
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean handleVideoModeTouchEvent(MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (isEnabled()) {
|
||||
startAnimation(shrinkAnimation);
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
break;
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
case MotionEvent.ACTION_UP:
|
||||
if (!isRecordingVideo) {
|
||||
startAnimation(growAnimation);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
@ -1,71 +0,0 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
|
||||
import android.view.Window
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
|
||||
/**
|
||||
* Modifies screen brightness to increase to a max of 66% if lower than that for optimal picture
|
||||
* taking conditions. This brightness is only applied when the front-facing camera is selected.
|
||||
*/
|
||||
class CameraScreenBrightnessController(
|
||||
private val window: Window,
|
||||
private val cameraStateProvider: CameraStateProvider
|
||||
) : DefaultLifecycleObserver {
|
||||
|
||||
companion object {
|
||||
private const val FRONT_CAMERA_BRIGHTNESS = 0.66f
|
||||
}
|
||||
|
||||
private val originalBrightness: Float by lazy { window.attributes.screenBrightness }
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
onCameraDirectionChanged(cameraStateProvider.isFrontFacingCameraSelected())
|
||||
onCameraFlashChanged(cameraStateProvider.isFlashEnabled())
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
disableBrightness()
|
||||
}
|
||||
|
||||
/**
|
||||
* Because setting camera direction is an asynchronous action, we cannot rely on
|
||||
* the `CameraDirectionProvider` at this point.
|
||||
*/
|
||||
fun onCameraDirectionChanged(isFrontFacing: Boolean) {
|
||||
if (isFrontFacing && cameraStateProvider.isFlashEnabled()) {
|
||||
enableBrightness()
|
||||
} else {
|
||||
disableBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
fun onCameraFlashChanged(isFlashEnabled: Boolean) {
|
||||
if (isFlashEnabled && cameraStateProvider.isFrontFacingCameraSelected()) {
|
||||
enableBrightness()
|
||||
} else {
|
||||
disableBrightness()
|
||||
}
|
||||
}
|
||||
|
||||
private fun enableBrightness() {
|
||||
if (originalBrightness < FRONT_CAMERA_BRIGHTNESS) {
|
||||
window.attributes = window.attributes.apply {
|
||||
screenBrightness = FRONT_CAMERA_BRIGHTNESS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableBrightness() {
|
||||
if (window.attributes.screenBrightness == FRONT_CAMERA_BRIGHTNESS) {
|
||||
window.attributes = window.attributes.apply {
|
||||
screenBrightness = originalBrightness
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface CameraStateProvider {
|
||||
fun isFrontFacingCameraSelected(): Boolean
|
||||
fun isFlashEnabled(): Boolean
|
||||
}
|
||||
}
|
||||
@ -23,6 +23,7 @@ import androidx.transition.AutoTransition
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.animation.ArgbEvaluatorCompat
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import org.signal.camera.CameraDisplay
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.util.BreakIteratorCompat
|
||||
import org.signal.core.util.Debouncer
|
||||
@ -42,7 +43,6 @@ import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardEventViewModel
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.EmojiKeyboardPageFragment
|
||||
import org.thoughtcrime.securesms.keyboard.emoji.search.EmojiSearchFragment
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil
|
||||
import org.thoughtcrime.securesms.mediasend.CameraDisplay
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult
|
||||
import org.thoughtcrime.securesms.mediasend.v2.review.MediaReviewFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.text.TextStoryPostCreationViewModel
|
||||
|
||||
@ -11,11 +11,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.signal.core.ui.permissions.Permissions
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.mediasend.CameraFragment
|
||||
import org.signal.mediasend.MediaConstraints
|
||||
import org.signal.mediasend.capture.CameraFragment
|
||||
import org.signal.mediasend.capture.CameraXFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.settings.app.AppSettingsActivity
|
||||
import org.thoughtcrime.securesms.mediasend.CameraXFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionNavigator
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
|
||||
@ -17,6 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||
import org.signal.camera.CameraDisplay
|
||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey
|
||||
@ -25,7 +26,6 @@ import org.thoughtcrime.securesms.databinding.StoriesTextPostCreationFragmentBin
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreview
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewState
|
||||
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModelV2
|
||||
import org.thoughtcrime.securesms.mediasend.CameraDisplay
|
||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
import org.thoughtcrime.securesms.mediasend.v2.stories.StoriesMultiselectForwardActivity
|
||||
|
||||
@ -7,6 +7,7 @@ package org.thoughtcrime.securesms.mediasend.v3
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@ -16,6 +17,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.signal.core.models.media.Media
|
||||
import org.signal.core.models.media.MediaFolder
|
||||
import org.signal.core.util.asListContains
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.mediasend.EditorState
|
||||
import org.signal.mediasend.MediaConstraints
|
||||
@ -32,6 +34,7 @@ import org.thoughtcrime.securesms.conversation.MessageSendType
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.model.IdentityRecord
|
||||
import org.thoughtcrime.securesms.dependencies.AppDependencies
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.mediasend.MediaRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionRepository
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaValidator
|
||||
@ -43,6 +46,7 @@ import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.scribbles.ImageEditorFragment
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.coroutines.resume
|
||||
@ -58,6 +62,12 @@ object MediaSendV3Repository : MediaSendRepository {
|
||||
private val legacyRepository = MediaSelectionRepository(appContext)
|
||||
private val mediaRepository = MediaRepository()
|
||||
|
||||
override var isCameraFacingFront: Boolean
|
||||
get() = SignalStore.misc.isCameraFacingFront
|
||||
set(value) {
|
||||
SignalStore.misc.isCameraFacingFront = value
|
||||
}
|
||||
|
||||
override suspend fun getFolders(): List<MediaFolder> = suspendCancellableCoroutine { continuation ->
|
||||
mediaRepository.getFolders(appContext) { folders ->
|
||||
continuation.resume(folders)
|
||||
@ -182,6 +192,10 @@ object MediaSendV3Repository : MediaSendRepository {
|
||||
return PartAuthority.getAttachmentStream(context, uri)
|
||||
}
|
||||
|
||||
override fun isMixedModeAvailable(): Boolean {
|
||||
return !RemoteConfig.cameraXMixedModelBlocklist.asListContains(Build.MODEL)
|
||||
}
|
||||
|
||||
private fun resolveSendType(sendType: Int): MessageSendType {
|
||||
return when (sendType) {
|
||||
else -> MessageSendType.SignalMessageSendType
|
||||
|
||||
@ -96,8 +96,8 @@ public final class PaymentsTransferFragment extends LoggingFragment {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs_access_to_the_camera), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, getParentFragmentManager())
|
||||
.withRationaleDialog(getString(org.signal.mediasend.R.string.CameraXFragment_allow_access_camera), getString(R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs_access_to_the_camera), null, org.signal.mediasend.R.string.CameraXFragment_allow_access_camera, org.signal.mediasend.R.string.CameraXFragment_to_scan_qr_codes, getParentFragmentManager())
|
||||
.onAllGranted(() -> SafeNavigation.safeNavigate(Navigation.findNavController(requireView()), R.id.action_paymentsTransfer_to_paymentsScanQr))
|
||||
.onAnyDenied(() -> Toast.makeText(requireContext(), R.string.PaymentsTransferFragment__to_scan_a_qr_code_signal_needs_access_to_the_camera, Toast.LENGTH_LONG).show())
|
||||
.execute();
|
||||
|
||||
@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyParcelable
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.signal.mediasend.R as MediaSendR
|
||||
|
||||
/**
|
||||
* Fragment to assist user in verifying recipient identity utilizing keys.
|
||||
@ -106,8 +107,8 @@ class VerifyIdentityFragment : Fragment(R.layout.fragment_container), ScanListen
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.CAMERA)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_camera), getString(R.string.CameraXFragment_to_scan_qr_code_allow_camera), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied), null, R.string.CameraXFragment_allow_access_camera, R.string.CameraXFragment_to_scan_qr_codes, getParentFragmentManager())
|
||||
.withRationaleDialog(getString(MediaSendR.string.CameraXFragment_allow_access_camera), getString(MediaSendR.string.CameraXFragment_to_scan_qr_code_allow_camera), R.drawable.ic_camera_24)
|
||||
.withPermanentDenialDialog(getString(R.string.VerifyIdentityActivity_signal_needs_the_camera_permission_in_order_to_scan_a_qr_code_but_it_has_been_permanently_denied), null, MediaSendR.string.CameraXFragment_allow_access_camera, MediaSendR.string.CameraXFragment_to_scan_qr_codes, getParentFragmentManager())
|
||||
.onAllGranted {
|
||||
childFragmentManager.beginTransaction()
|
||||
.setCustomAnimations(R.anim.slide_from_top, R.anim.slide_to_bottom, R.anim.slide_from_bottom, R.anim.slide_to_top)
|
||||
@ -115,7 +116,7 @@ class VerifyIdentityFragment : Fragment(R.layout.fragment_container), ScanListen
|
||||
.addToBackStack(null)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.onAnyDenied { Toast.makeText(requireContext(), MediaSendR.string.CameraXFragment_signal_needs_camera_access_scan_qr_code, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
|
||||
|
||||
@ -1,100 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.CameraButtonView
|
||||
android:id="@+id/camera_capture_button"
|
||||
android:layout_width="@dimen/camera_capture_button_size"
|
||||
android:layout_height="@dimen/camera_capture_button_size"
|
||||
android:contentDescription="@string/CameraXFragment_capture_description"
|
||||
app:imageCaptureSize="@dimen/camera_capture_image_button_size"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:recordSize="54dp" />
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.camerax.CameraXFlashToggleView
|
||||
android:id="@+id/camera_flash_button"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="@drawable/circle_transparent_black_40"
|
||||
android:contentDescription="@string/CameraControls_toggle_flash_mode_accessibility_label"
|
||||
android:padding="6dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/camerax_flash_toggle"
|
||||
android:tint="@color/core_white"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/camera_flip_button"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginBottom="36dp"
|
||||
android:background="@drawable/media_selection_camera_switch_background"
|
||||
android:contentDescription="@string/CameraXFragment_change_camera_description"
|
||||
android:scaleType="centerInside"
|
||||
android:visibility="gone"
|
||||
android:tint="@color/core_white"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:srcCompat="@drawable/symbol_switch_24"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/camera_gallery_button_background"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginBottom="36dp"
|
||||
android:background="@drawable/circle_tintable"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/camera_gallery_button"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:contentDescription="@string/CameraXFragment_open_gallery_description"
|
||||
android:padding="2dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle"
|
||||
tools:src="@color/black" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton
|
||||
android:id="@+id/camera_review_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:background="@drawable/v2_media_count_indicator_background"
|
||||
android:minHeight="44dp"
|
||||
android:visibility="gone"
|
||||
android:contentDescription="@string/CameraControls_continue_button_accessibility_label"
|
||||
app:layout_constraintBottom_toBottomOf="@id/camera_capture_button"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/camera_capture_button"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:id="@+id/camera_selfie_flash"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:alpha="0"
|
||||
android:background="@color/white"
|
||||
android:contentDescription="@string/CameraControls_capture_button_accessibility_label"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/camera_preview_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<TextureView
|
||||
android:id="@+id/camera_preview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/camera_controls_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/camera_preview_parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -1,81 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:viewBindingIgnore="true">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/camerax_camera_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardElevation="0dp"
|
||||
app:layout_constraintDimensionRatio="9:16"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<androidx.camera.view.PreviewView
|
||||
android:id="@+id/camerax_camera"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="top"
|
||||
app:implementationMode="compatible" />
|
||||
<ImageView
|
||||
android:id="@+id/camerax_focus_indicator"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:src="@drawable/focus_indicator"
|
||||
android:scaleType="fitCenter"
|
||||
android:visibility="gone"/>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/camerax_controls_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/camerax_camera_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/missing_permissions_container"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="-50dp"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/permission_camera" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/missing_permissions_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="20dp"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:text="@string/CameraXFragment_to_capture_photos_and_video_allow_camera" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/allow_access_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
style="@style/Signal.Widget.Button.Large.Tonal"
|
||||
android:text="@string/CameraXFragment_allow_access" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -30,9 +30,6 @@
|
||||
<dimen name="album_5_cell_size_big">104dp</dimen>
|
||||
<dimen name="album_5_cell_size_small">69dp</dimen>
|
||||
|
||||
<dimen name="camera_capture_button_size">124dp</dimen>
|
||||
<dimen name="camera_capture_image_button_size">76dp</dimen>
|
||||
|
||||
<dimen name="message_corner_radius">18dp</dimen>
|
||||
<dimen name="message_corner_collapse_radius">4dp</dimen>
|
||||
<dimen name="message_bubble_corner_radius">2dp</dimen>
|
||||
|
||||
@ -275,60 +275,6 @@
|
||||
<!-- Unknown people row info dialog action to close the dialog -->
|
||||
<string name="CallInfoView__got_it">Got it</string>
|
||||
|
||||
<!-- CameraFragment -->
|
||||
<!-- Toasted when user device does not support video recording -->
|
||||
<string name="CameraFragment__video_recording_is_not_supported_on_your_device">Video recording is not supported on your device</string>
|
||||
|
||||
<!-- CameraXFragment -->
|
||||
<string name="CameraXFragment_tap_for_photo_hold_for_video">Tap for photo, hold for video</string>
|
||||
<!-- Accessibility content description to describe the capture button when taking an image/video -->
|
||||
<string name="CameraXFragment_capture_description">Capture</string>
|
||||
<string name="CameraXFragment_change_camera_description">Change camera</string>
|
||||
<string name="CameraXFragment_open_gallery_description">Open gallery</string>
|
||||
<!-- Button text asking for access to camera permissions -->
|
||||
<string name="CameraXFragment_allow_access">Allow access</string>
|
||||
<!-- Dialog title asking users for camera and microphone permission -->
|
||||
<string name="CameraXFragment_allow_access_camera_microphone">Allow access to your camera and microphone</string>
|
||||
<!-- Dialog title asking users for camera permission -->
|
||||
<string name="CameraXFragment_allow_access_camera">Allow access to your camera</string>
|
||||
<!-- Dialog title asking users for microphone permission -->
|
||||
<string name="CameraXFragment_allow_access_microphone">Allow access to your microphone</string>
|
||||
<!-- Text explaining why Signal needs camera access in order to take photos and videos -->
|
||||
<string name="CameraXFragment_to_capture_photos_and_video_allow_camera">To capture photos and video, allow Signal access to the camera.</string>
|
||||
<!-- Text explaining why Signal needs camera and microphone access in order to take photos and videos -->
|
||||
<string name="CameraXFragment_to_capture_photos_and_video_allow_camera_microphone">To capture photos and video, allow Signal access to the camera and microphone.</string>
|
||||
<!-- Text explaining why Signal needs microphone access to take videos -->
|
||||
<string name="CameraXFragment_to_capture_videos_with_sound">To capture videos with sound, allow Signal access to your microphone.</string>
|
||||
<!-- Text explaining why Signal needs camera access to scan QR codes -->
|
||||
<string name="CameraXFragment_to_scan_qr_code_allow_camera">To scan a QR code, allow Signal access to the camera.</string>
|
||||
<!-- Toast dialog explaining why Signal needs camera permissions when capturing photos -->
|
||||
<string name="CameraXFragment_signal_needs_camera_access_capture_photos">Signal needs camera access to capture photos</string>
|
||||
<!-- Toast dialog explaining why Signal needs camera permissions when scanning QR codes -->
|
||||
<string name="CameraXFragment_signal_needs_camera_access_scan_qr_code">Signal needs camera access to scan QR codes</string>
|
||||
<!-- Toast dialog explaining why Signal needs microphone permissions -->
|
||||
<string name="CameraXFragment_signal_needs_microphone_access_video">Signal needs microphone access to capture video</string>
|
||||
<!-- Dialog description that explains the steps needed to give camera permission -->
|
||||
<string name="CameraXFragment_to_capture_photos">To capture photos in Signal:</string>
|
||||
<!-- Dialog description that explains the steps needed to give camera and microphone permission -->
|
||||
<string name="CameraXFragment_to_capture_photos_videos">To capture photos and videos in Signal:</string>
|
||||
<!-- Dialog description that explains the steps needed to give microphone permission -->
|
||||
<string name="CameraXFragment_to_capture_videos">To capture videos with sound:</string>
|
||||
<!-- Dialog description that explains the steps needed to give Signal camera permissions -->
|
||||
<string name="CameraXFragment_to_scan_qr_codes">To scan QR codes:</string>
|
||||
<!-- Error message shown when we try to take a photo, but fail -->
|
||||
<string name="CameraXFragment_photo_capture_failed">Failed to capture photo. Please try again.</string>
|
||||
<!-- Error message shown when we try to take a photo, but fail when trying to process it (convert it into something the user can see). -->
|
||||
<string name="CameraXFragment_photo_processing_failed">Failed to process photo. Please try again.</string>
|
||||
<!-- Accessibility label for the switch camera button -->
|
||||
<string name="CameraXFragment_switch_camera">Switch camera</string>
|
||||
<!-- Accessibility label for flash button when flash is off -->
|
||||
<string name="CameraXFragment_flash_off">Flash off</string>
|
||||
<!-- Accessibility label for flash button when flash is on -->
|
||||
<string name="CameraXFragment_flash_on">Flash on</string>
|
||||
<!-- Accessibility label for flash button when flash is set to auto -->
|
||||
<string name="CameraXFragment_flash_auto">Flash auto</string>
|
||||
<!-- Accessibility label for the send button in media selection -->
|
||||
<string name="CameraXFragment_send">Send</string>
|
||||
|
||||
<string name="CameraContacts__menu_search">Search</string>
|
||||
|
||||
@ -530,7 +476,6 @@
|
||||
<string name="ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video">Signal needs the Camera permission to take photos or video, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Camera\".</string>
|
||||
<string name="ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video">Signal needs Camera permissions to take photos or video</string>
|
||||
<string name="ConversationActivity_enable_the_microphone_permission_to_capture_videos_with_sound">Enable the microphone permission to capture videos with sound.</string>
|
||||
<string name="ConversationActivity_signal_needs_the_recording_permissions_to_capture_video">Signal needs microphone permissions to record videos, but they have been denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\" and \"Camera\".</string>
|
||||
<string name="ConversationActivity_signal_needs_recording_permissions_to_capture_video">Signal needs microphone permissions to record videos.</string>
|
||||
|
||||
<string name="ConversationActivity_quoted_contact_message">%1$s %2$s</string>
|
||||
|
||||
@ -64,6 +64,7 @@ enum class SignalIcons(private val icon: SignalIcon) : SignalIcon by icon {
|
||||
Link(icon(R.drawable.symbol_link_24)),
|
||||
Lock(icon(R.drawable.symbol_lock_24)),
|
||||
Maximize(icon(R.drawable.symbol_maximize_24)),
|
||||
Mic(icon(R.drawable.symbol_mic_24)),
|
||||
MoreVertical(icon(R.drawable.symbol_more_vertical_24)),
|
||||
PersonCircle(icon(R.drawable.symbol_person_circle_24)),
|
||||
Phone(icon(R.drawable.symbol_phone_24)),
|
||||
|
||||
12
core/ui/src/main/res/drawable/symbol_mic_24.xml
Normal file
12
core/ui/src/main/res/drawable/symbol_mic_24.xml
Normal file
@ -0,0 +1,12 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12 1.13C9.3 1.13 7.12 3.3 7.12 6v5.5c0 2.7 2.19 4.88 4.88 4.88 2.7 0 4.88-2.19 4.88-4.88V6c0-2.7-2.19-4.88-4.88-4.88ZM8.87 6c0-1.73 1.4-3.13 3.13-3.13s3.13 1.4 3.13 3.13v5.5c0 1.73-1.4 3.13-3.13 3.13s-3.13-1.4-3.13-3.13V6Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M7.75 21.13c-0.48 0-0.88 0.39-0.88 0.87s0.4 0.88 0.88 0.88h8.5c0.48 0 0.88-0.4 0.88-0.88s-0.4-0.88-0.88-0.88h-3.38v-1.29c4.22-0.44 7.5-4 7.5-8.33v-1c0-0.48-0.39-0.88-0.87-0.88s-0.88 0.4-0.88 0.88v1c0 3.66-2.96 6.63-6.62 6.63s-6.63-2.97-6.63-6.63v-1c0-0.48-0.39-0.88-0.87-0.88s-0.88 0.4-0.88 0.88v1c0 4.33 3.3 7.9 7.5 8.33v1.3H7.75Z"/>
|
||||
</vector>
|
||||
@ -16,8 +16,8 @@ import org.junit.Assume
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.signal.core.util.video.TranscodingPreset
|
||||
import org.thoughtcrime.securesms.video.StreamingTranscoder
|
||||
import org.thoughtcrime.securesms.video.TranscodingPreset
|
||||
import org.thoughtcrime.securesms.video.exceptions.VideoSourceException
|
||||
import org.thoughtcrime.securesms.video.videoconverter.exceptions.CodecUnavailableException
|
||||
import org.thoughtcrime.securesms.video.videoconverter.exceptions.EncodingException
|
||||
|
||||
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.camera
|
||||
|
||||
import android.app.Application
|
||||
|
||||
/**
|
||||
* Camera Feature Module dependencies
|
||||
*/
|
||||
object CameraDependencies {
|
||||
private lateinit var _application: Application
|
||||
private lateinit var _provider: Provider
|
||||
|
||||
@Synchronized
|
||||
fun init(application: Application, provider: Provider) {
|
||||
if (this::_application.isInitialized || this::_provider.isInitialized) {
|
||||
return
|
||||
}
|
||||
|
||||
_application = application
|
||||
_provider = provider
|
||||
}
|
||||
|
||||
val application
|
||||
get() = _application
|
||||
|
||||
fun isStoriesFeatureEnabled(): Boolean {
|
||||
return _provider.isStoriesFeatureEnabled()
|
||||
}
|
||||
|
||||
interface Provider {
|
||||
fun isStoriesFeatureEnabled(): Boolean
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,9 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.camera
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.res.Resources
|
||||
@ -6,8 +11,6 @@ import androidx.annotation.Dimension
|
||||
import androidx.annotation.Px
|
||||
import androidx.window.layout.WindowMetricsCalculator
|
||||
import org.signal.core.util.dp
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
|
||||
/**
|
||||
* Description of the Camera Viewport, Controls, and Toggle position information.
|
||||
@ -17,7 +20,7 @@ enum class CameraDisplay(
|
||||
val roundViewFinderCorners: Boolean,
|
||||
private val withTogglePositionInfo: PositionInfo,
|
||||
private val withoutTogglePositionInfo: PositionInfo,
|
||||
@Dimension(unit = Dimension.DP) private val toggleBottomMargin: Int
|
||||
@get:Dimension(unit = Dimension.DP) private val toggleBottomMargin: Int
|
||||
) {
|
||||
DISPLAY_20_9(
|
||||
aspectRatio = 9f / 20f,
|
||||
@ -89,7 +92,7 @@ enum class CameraDisplay(
|
||||
|
||||
@JvmOverloads
|
||||
@Px
|
||||
fun getCameraCaptureMarginBottom(resources: Resources, storiesEnabled: Boolean = Stories.isFeatureEnabled()): Int {
|
||||
fun getCameraCaptureMarginBottom(resources: Resources, storiesEnabled: Boolean = CameraDependencies.isStoriesFeatureEnabled()): Int {
|
||||
val positionInfo = if (storiesEnabled) withTogglePositionInfo else withoutTogglePositionInfo
|
||||
|
||||
return positionInfo.cameraCaptureMarginBottomDp.dp - getCameraButtonSizeOffset(resources)
|
||||
@ -97,14 +100,14 @@ enum class CameraDisplay(
|
||||
|
||||
@JvmOverloads
|
||||
@Px
|
||||
fun getCameraViewportMarginBottom(storiesEnabled: Boolean = Stories.isFeatureEnabled()): Int {
|
||||
fun getCameraViewportMarginBottom(storiesEnabled: Boolean = CameraDependencies.isStoriesFeatureEnabled()): Int {
|
||||
val positionInfo = if (storiesEnabled) withTogglePositionInfo else withoutTogglePositionInfo
|
||||
|
||||
return positionInfo.cameraViewportMarginBottomDp.dp
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun getCameraViewportGravity(storiesEnabled: Boolean = Stories.isFeatureEnabled()): CameraViewportGravity {
|
||||
fun getCameraViewportGravity(storiesEnabled: Boolean = CameraDependencies.isStoriesFeatureEnabled()): CameraViewportGravity {
|
||||
val positionInfo = if (storiesEnabled) withTogglePositionInfo else withoutTogglePositionInfo
|
||||
|
||||
return positionInfo.cameraViewportGravity
|
||||
9
feature/camera/src/main/res/values/dimen.xml
Normal file
9
feature/camera/src/main/res/values/dimen.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2026 Signal Messenger, LLC
|
||||
~ SPDX-License-Identifier: AGPL-3.0-only
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<dimen name="camera_capture_button_size">124dp</dimen>
|
||||
<dimen name="camera_capture_image_button_size">76dp</dimen>
|
||||
</resources>
|
||||
@ -35,6 +35,7 @@ dependencies {
|
||||
implementation(project(":lib:image-editor"))
|
||||
implementation(project(":lib:glide"))
|
||||
implementation(project(":lib:video"))
|
||||
implementation(project(":feature:camera"))
|
||||
|
||||
// Compose BOM
|
||||
platform(libs.androidx.compose.bom).let { composeBom ->
|
||||
@ -67,6 +68,9 @@ dependencies {
|
||||
// Media
|
||||
implementation(libs.androidx.media3.exoplayer)
|
||||
|
||||
// CameraX
|
||||
implementation(libs.androidx.camera.core)
|
||||
|
||||
// Testing
|
||||
testImplementation(testLibs.junit.junit)
|
||||
testImplementation(testLibs.mockk)
|
||||
|
||||
@ -3,12 +3,10 @@
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend.capture;
|
||||
package org.signal.mediasend;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
|
||||
public interface CameraFragment {
|
||||
@ -102,6 +102,10 @@ interface MediaSendRepository {
|
||||
fun observeRecipientValid(recipientId: MediaRecipientId): Flow<Boolean>
|
||||
|
||||
fun getAttachmentStream(context: Context, uri: Uri): InputStream
|
||||
|
||||
fun isMixedModeAvailable(): Boolean
|
||||
|
||||
var isCameraFacingFront: Boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
package org.thoughtcrime.securesms.video;
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.mediasend.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.video.videoconverter.utils.VideoConstants;
|
||||
|
||||
public final class VideoUtil {
|
||||
@ -1,4 +1,9 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.signal.mediasend.capture
|
||||
|
||||
import android.Manifest
|
||||
import android.content.Context
|
||||
@ -8,9 +13,9 @@ import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.system.Os
|
||||
import android.system.OsConstants
|
||||
import android.widget.Toast
|
||||
import android.widget.Toast.LENGTH_SHORT
|
||||
import android.widget.Toast.makeText
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
@ -41,10 +46,14 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.delay
|
||||
import org.signal.camera.CameraCaptureMode
|
||||
import org.signal.camera.CameraDependencies
|
||||
import org.signal.camera.CameraDisplay
|
||||
import org.signal.camera.CameraScreen
|
||||
import org.signal.camera.CameraScreenEvents
|
||||
import org.signal.camera.CameraScreenViewModel
|
||||
@ -57,19 +66,16 @@ import org.signal.camera.hud.StandardCameraHudEvents
|
||||
import org.signal.camera.hud.StringResources
|
||||
import org.signal.core.ui.BottomSheetUtil
|
||||
import org.signal.core.ui.compose.ComposeFragment
|
||||
import org.signal.core.ui.permissions.PermissionDeniedBottomSheet.Companion.showPermissionFragment
|
||||
import org.signal.core.ui.compose.Previews
|
||||
import org.signal.core.ui.permissions.PermissionDeniedBottomSheet
|
||||
import org.signal.core.ui.permissions.Permissions
|
||||
import org.signal.core.util.MemoryFileDescriptor
|
||||
import org.signal.core.util.asListContains
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.mediasend.CameraFragment
|
||||
import org.signal.mediasend.MediaConstraints
|
||||
import org.signal.mediasend.capture.CameraFragment
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.R.string.CameraFragment__video_recording_is_not_supported_on_your_device
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.RemoteConfig
|
||||
import org.thoughtcrime.securesms.video.VideoUtil
|
||||
import org.signal.mediasend.MediaSendDependencies
|
||||
import org.signal.mediasend.R
|
||||
import org.signal.mediasend.VideoUtil
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
@ -199,13 +205,13 @@ class CameraXFragment : ComposeFragment(), CameraFragment {
|
||||
}
|
||||
.onSomePermanentlyDenied { deniedPermissions ->
|
||||
if (deniedPermissions.containsAll(listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO))) {
|
||||
showPermissionFragment(
|
||||
PermissionDeniedBottomSheet.showPermissionFragment(
|
||||
R.string.CameraXFragment_allow_access_camera_microphone,
|
||||
R.string.CameraXFragment_to_capture_photos_videos,
|
||||
false
|
||||
).show(parentFragmentManager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG)
|
||||
} else if (deniedPermissions.contains(Manifest.permission.CAMERA)) {
|
||||
showPermissionFragment(
|
||||
PermissionDeniedBottomSheet.showPermissionFragment(
|
||||
R.string.CameraXFragment_allow_access_camera,
|
||||
R.string.CameraXFragment_to_capture_photos_videos,
|
||||
false
|
||||
@ -253,8 +259,14 @@ class CameraXFragment : ComposeFragment(), CameraFragment {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.RECORD_AUDIO)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_microphone), getString(R.string.CameraXFragment_to_capture_videos_with_sound), R.drawable.ic_mic_24)
|
||||
.withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_recording_permissions_to_capture_video), null, R.string.CameraXFragment_allow_access_microphone, R.string.CameraXFragment_to_capture_videos, parentFragmentManager)
|
||||
.withRationaleDialog(getString(R.string.CameraXFragment_allow_access_microphone), getString(R.string.CameraXFragment_to_capture_videos_with_sound), org.signal.core.ui.R.drawable.symbol_mic_24)
|
||||
.withPermanentDenialDialog(
|
||||
getString(R.string.CameraXFragment_signal_needs_the_recording_permissions_to_capture_video),
|
||||
null,
|
||||
R.string.CameraXFragment_allow_access_microphone,
|
||||
R.string.CameraXFragment_to_capture_videos,
|
||||
parentFragmentManager
|
||||
)
|
||||
.onAnyDenied { Toast.makeText(requireContext(), R.string.CameraXFragment_signal_needs_microphone_access_video, Toast.LENGTH_LONG).show() }
|
||||
.execute()
|
||||
}
|
||||
@ -301,7 +313,7 @@ class CameraXFragment : ComposeFragment(), CameraFragment {
|
||||
|
||||
val isMixedModeSupported = isVideoSupported &&
|
||||
CameraXUtil.isMixedModeSupported(requireContext()) &&
|
||||
!RemoteConfig.cameraXMixedModelBlocklist.asListContains(Build.MODEL)
|
||||
MediaSendDependencies.mediaSendRepository.isMixedModeAvailable()
|
||||
|
||||
return when {
|
||||
isMixedModeSupported -> CameraCaptureMode.ImageAndVideoSimultaneous
|
||||
@ -326,7 +338,7 @@ private fun CameraXScreen(
|
||||
createVideoFileDescriptor: () -> ParcelFileDescriptor?,
|
||||
getMaxVideoDurationInSeconds: () -> Int,
|
||||
cameraDisplay: CameraDisplay,
|
||||
storiesEnabled: Boolean = Stories.isFeatureEnabled()
|
||||
storiesEnabled: Boolean = CameraDependencies.isStoriesFeatureEnabled()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val cameraViewModel: CameraScreenViewModel = viewModel()
|
||||
@ -334,7 +346,7 @@ private fun CameraXScreen(
|
||||
var hasPermission by remember { mutableStateOf(hasCameraPermission()) }
|
||||
|
||||
LaunchedEffect(cameraViewModel) {
|
||||
val lensFacing = if (SignalStore.misc.isCameraFacingFront) {
|
||||
val lensFacing = if (MediaSendDependencies.mediaSendRepository.isCameraFacingFront) {
|
||||
CameraSelector.LENS_FACING_FRONT
|
||||
} else {
|
||||
CameraSelector.LENS_FACING_BACK
|
||||
@ -345,7 +357,7 @@ private fun CameraXScreen(
|
||||
LaunchedEffect(cameraViewModel) {
|
||||
snapshotFlow { cameraState.lensFacing }
|
||||
.collect { lensFacing ->
|
||||
SignalStore.misc.isCameraFacingFront = lensFacing == CameraSelector.LENS_FACING_FRONT
|
||||
MediaSendDependencies.mediaSendRepository.isCameraFacingFront = lensFacing == CameraSelector.LENS_FACING_FRONT
|
||||
}
|
||||
}
|
||||
|
||||
@ -365,7 +377,7 @@ private fun CameraXScreen(
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
while (true) {
|
||||
kotlinx.coroutines.delay(500)
|
||||
delay(500)
|
||||
val newHasPermission = hasCameraPermission()
|
||||
if (newHasPermission != hasPermission) {
|
||||
hasPermission = newHasPermission
|
||||
@ -547,11 +559,11 @@ private fun handleHudEvent(
|
||||
}
|
||||
)
|
||||
} else {
|
||||
makeText(context, CameraFragment__video_recording_is_not_supported_on_your_device, LENGTH_SHORT)
|
||||
Toast.makeText(context, R.string.CameraFragment__video_recording_is_not_supported_on_your_device, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
makeText(context, CameraFragment__video_recording_is_not_supported_on_your_device, LENGTH_SHORT)
|
||||
Toast.makeText(context, R.string.CameraFragment__video_recording_is_not_supported_on_your_device, Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
@ -605,7 +617,7 @@ private fun handleVideoCaptured(result: VideoCaptureResult, controller: CameraFr
|
||||
result.fileDescriptor?.let { parcelFd ->
|
||||
try {
|
||||
// Seek to beginning before reading
|
||||
android.system.Os.lseek(parcelFd.fileDescriptor, 0, android.system.OsConstants.SEEK_SET)
|
||||
Os.lseek(parcelFd.fileDescriptor, 0, OsConstants.SEEK_SET)
|
||||
controller?.onVideoCaptured(parcelFd.fileDescriptor)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Failed to seek video file descriptor", e)
|
||||
@ -621,7 +633,7 @@ private fun handleVideoCaptured(result: VideoCaptureResult, controller: CameraFr
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.compose.ui.tooling.preview.Preview(
|
||||
@Preview(
|
||||
name = "20:9 Display",
|
||||
showBackground = true,
|
||||
widthDp = 360,
|
||||
@ -629,7 +641,7 @@ private fun handleVideoCaptured(result: VideoCaptureResult, controller: CameraFr
|
||||
)
|
||||
@Composable
|
||||
private fun CameraXScreenPreview_20_9() {
|
||||
org.signal.core.ui.compose.Previews.Preview {
|
||||
Previews.Preview {
|
||||
CameraXScreen(
|
||||
controller = null,
|
||||
isVideoEnabled = true,
|
||||
@ -649,7 +661,7 @@ private fun CameraXScreenPreview_20_9() {
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.compose.ui.tooling.preview.Preview(
|
||||
@Preview(
|
||||
name = "19:9 Display",
|
||||
showBackground = true,
|
||||
widthDp = 360,
|
||||
@ -657,7 +669,7 @@ private fun CameraXScreenPreview_20_9() {
|
||||
)
|
||||
@Composable
|
||||
private fun CameraXScreenPreview_19_9() {
|
||||
org.signal.core.ui.compose.Previews.Preview {
|
||||
Previews.Preview {
|
||||
CameraXScreen(
|
||||
controller = null,
|
||||
isVideoEnabled = true,
|
||||
@ -677,7 +689,7 @@ private fun CameraXScreenPreview_19_9() {
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.compose.ui.tooling.preview.Preview(
|
||||
@Preview(
|
||||
name = "18:9 Display",
|
||||
showBackground = true,
|
||||
widthDp = 360,
|
||||
@ -685,7 +697,7 @@ private fun CameraXScreenPreview_19_9() {
|
||||
)
|
||||
@Composable
|
||||
private fun CameraXScreenPreview_18_9() {
|
||||
org.signal.core.ui.compose.Previews.Preview {
|
||||
Previews.Preview {
|
||||
CameraXScreen(
|
||||
controller = null,
|
||||
isVideoEnabled = true,
|
||||
@ -705,7 +717,7 @@ private fun CameraXScreenPreview_18_9() {
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.compose.ui.tooling.preview.Preview(
|
||||
@Preview(
|
||||
name = "16:9 Display",
|
||||
showBackground = true,
|
||||
widthDp = 360,
|
||||
@ -713,7 +725,7 @@ private fun CameraXScreenPreview_18_9() {
|
||||
)
|
||||
@Composable
|
||||
private fun CameraXScreenPreview_16_9() {
|
||||
org.signal.core.ui.compose.Previews.Preview {
|
||||
Previews.Preview {
|
||||
CameraXScreen(
|
||||
controller = null,
|
||||
isVideoEnabled = true,
|
||||
@ -733,7 +745,7 @@ private fun CameraXScreenPreview_16_9() {
|
||||
}
|
||||
}
|
||||
|
||||
@androidx.compose.ui.tooling.preview.Preview(
|
||||
@Preview(
|
||||
name = "6:5 Display (Tablet)",
|
||||
showBackground = true,
|
||||
widthDp = 480,
|
||||
@ -741,7 +753,7 @@ private fun CameraXScreenPreview_16_9() {
|
||||
)
|
||||
@Composable
|
||||
private fun CameraXScreenPreview_6_5() {
|
||||
org.signal.core.ui.compose.Previews.Preview {
|
||||
Previews.Preview {
|
||||
CameraXScreen(
|
||||
controller = null,
|
||||
isVideoEnabled = true,
|
||||
@ -28,4 +28,61 @@
|
||||
<string name="MediaCaptureScreen__text_story">Text Story</string>
|
||||
<!-- Video editor play button content description -->
|
||||
<string name="VideoEditorHud_play_video_description">Play video</string>
|
||||
|
||||
<!-- CameraFragment -->
|
||||
<!-- Toasted when user device does not support video recording -->
|
||||
<string name="CameraFragment__video_recording_is_not_supported_on_your_device">Video recording is not supported on your device</string>
|
||||
|
||||
<!-- CameraXFragment -->
|
||||
<string name="CameraXFragment_tap_for_photo_hold_for_video">Tap for photo, hold for video</string>
|
||||
<!-- Accessibility content description to describe the capture button when taking an image/video -->
|
||||
<string name="CameraXFragment_capture_description">Capture</string>
|
||||
<string name="CameraXFragment_change_camera_description">Change camera</string>
|
||||
<string name="CameraXFragment_open_gallery_description">Open gallery</string>
|
||||
<!-- Button text asking for access to camera permissions -->
|
||||
<string name="CameraXFragment_allow_access">Allow access</string>
|
||||
<!-- Dialog title asking users for camera and microphone permission -->
|
||||
<string name="CameraXFragment_allow_access_camera_microphone">Allow access to your camera and microphone</string>
|
||||
<!-- Dialog title asking users for camera permission -->
|
||||
<string name="CameraXFragment_allow_access_camera">Allow access to your camera</string>
|
||||
<!-- Dialog title asking users for microphone permission -->
|
||||
<string name="CameraXFragment_allow_access_microphone">Allow access to your microphone</string>
|
||||
<!-- Text explaining why Signal needs camera access in order to take photos and videos -->
|
||||
<string name="CameraXFragment_to_capture_photos_and_video_allow_camera">To capture photos and video, allow Signal access to the camera.</string>
|
||||
<!-- Text explaining why Signal needs camera and microphone access in order to take photos and videos -->
|
||||
<string name="CameraXFragment_to_capture_photos_and_video_allow_camera_microphone">To capture photos and video, allow Signal access to the camera and microphone.</string>
|
||||
<!-- Text explaining why Signal needs microphone access to take videos -->
|
||||
<string name="CameraXFragment_to_capture_videos_with_sound">To capture videos with sound, allow Signal access to your microphone.</string>
|
||||
<!-- Text explaining why Signal needs camera access to scan QR codes -->
|
||||
<string name="CameraXFragment_to_scan_qr_code_allow_camera">To scan a QR code, allow Signal access to the camera.</string>
|
||||
<!-- Toast dialog explaining why Signal needs camera permissions when capturing photos -->
|
||||
<string name="CameraXFragment_signal_needs_camera_access_capture_photos">Signal needs camera access to capture photos</string>
|
||||
<!-- Toast dialog explaining why Signal needs camera permissions when scanning QR codes -->
|
||||
<string name="CameraXFragment_signal_needs_camera_access_scan_qr_code">Signal needs camera access to scan QR codes</string>
|
||||
<!-- Toast dialog explaining why Signal needs microphone permissions -->
|
||||
<string name="CameraXFragment_signal_needs_microphone_access_video">Signal needs microphone access to capture video</string>
|
||||
<!-- Dialog description that explains the steps needed to give camera permission -->
|
||||
<string name="CameraXFragment_to_capture_photos">To capture photos in Signal:</string>
|
||||
<!-- Dialog description that explains the steps needed to give camera and microphone permission -->
|
||||
<string name="CameraXFragment_to_capture_photos_videos">To capture photos and videos in Signal:</string>
|
||||
<!-- Dialog description that explains the steps needed to give microphone permission -->
|
||||
<string name="CameraXFragment_to_capture_videos">To capture videos with sound:</string>
|
||||
<!-- Dialog description that explains the steps needed to give Signal camera permissions -->
|
||||
<string name="CameraXFragment_to_scan_qr_codes">To scan QR codes:</string>
|
||||
<!-- Error message shown when we try to take a photo, but fail -->
|
||||
<string name="CameraXFragment_photo_capture_failed">Failed to capture photo. Please try again.</string>
|
||||
<!-- Error message shown when we try to take a photo, but fail when trying to process it (convert it into something the user can see). -->
|
||||
<string name="CameraXFragment_photo_processing_failed">Failed to process photo. Please try again.</string>
|
||||
<!-- Accessibility label for the switch camera button -->
|
||||
<string name="CameraXFragment_switch_camera">Switch camera</string>
|
||||
<!-- Accessibility label for flash button when flash is off -->
|
||||
<string name="CameraXFragment_flash_off">Flash off</string>
|
||||
<!-- Accessibility label for flash button when flash is on -->
|
||||
<string name="CameraXFragment_flash_on">Flash on</string>
|
||||
<!-- Accessibility label for flash button when flash is set to auto -->
|
||||
<string name="CameraXFragment_flash_auto">Flash auto</string>
|
||||
<!-- Accessibility label for the send button in media selection -->
|
||||
<string name="CameraXFragment_send">Send</string>
|
||||
<!-- Displayed in a permissions dialog when the user has denied access to hardware for image and video capture. -->
|
||||
<string name="CameraXFragment_signal_needs_the_recording_permissions_to_capture_video">Signal needs microphone permissions to record videos, but they have been denied. Please continue to app settings, select \"Permissions\", and enable \"Microphone\" and \"Camera\".</string>
|
||||
</resources>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user