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 175419a4b..28e250789 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/Camera1Fragment.java @@ -27,6 +27,7 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; import com.bumptech.glide.Glide; import com.bumptech.glide.load.MultiTransformation; @@ -45,7 +46,6 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.stories.viewer.page.StoryDisplay; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -72,6 +72,10 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, private OrderEnforcer orderEnforcer; private Camera1Controller.Properties properties; + private final Observer> thumbObserver = this::presentRecentItemThumbnail; + private boolean isThumbAvailable; + private boolean isMediaSelected; + public static Camera1Fragment newInstance() { return new Camera1Fragment(); } @@ -124,8 +128,6 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, GestureDetector gestureDetector = new GestureDetector(flipGestureListener); cameraPreview.setOnTouchListener((v, event) -> gestureDetector.onTouchEvent(event)); - controller.getMostRecentMediaItem().observe(getViewLifecycleOwner(), this::presentRecentItemThumbnail); - view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { // Let's assume portrait for now, so 9:16 float aspectRatio = CameraFragment.getAspectRatioForOrientation(getResources().getConfiguration().orientation); @@ -173,7 +175,14 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, orderEnforcer.reset(); } - @Override public void onDestroy() { + @Override + public void onDestroyView() { + super.onDestroyView(); + controller.getMostRecentMediaItem().removeObserver(thumbObserver); + } + + @Override + public void onDestroy() { super.onDestroy(); requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } @@ -251,6 +260,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, private void presentRecentItemThumbnail(Optional media) { if (media == null) { + isThumbAvailable = false; + updateGalleryVisibility(); return; } @@ -266,17 +277,36 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, thumbnail.setVisibility(View.GONE); thumbnail.setImageResource(0); } + + isThumbAvailable = media.isPresent(); + updateGalleryVisibility(); } @Override public void presentHud(int selectedMediaCount) { - MediaCountIndicatorButton countButton = controlsContainer.findViewById(R.id.camera_review_button); + MediaCountIndicatorButton countButton = controlsContainer.findViewById(R.id.camera_review_button); + View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); if (selectedMediaCount > 0) { countButton.setVisibility(View.VISIBLE); countButton.setCount(selectedMediaCount); + cameraGalleryContainer.setVisibility(View.GONE); } else { countButton.setVisibility(View.GONE); + cameraGalleryContainer.setVisibility(View.VISIBLE); + } + + isMediaSelected = selectedMediaCount > 0; + updateGalleryVisibility(); + } + + private void updateGalleryVisibility() { + View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); + + if (isMediaSelected || !isThumbAvailable) { + cameraGalleryContainer.setVisibility(View.GONE); + } else { + cameraGalleryContainer.setVisibility(View.VISIBLE); } } @@ -288,6 +318,9 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, View countButton = requireView().findViewById(R.id.camera_review_button); View toggleSpacer = requireView().findViewById(R.id.toggle_spacer); + controller.getMostRecentMediaItem().removeObserver(thumbObserver); + controller.getMostRecentMediaItem().observeForever(thumbObserver); + if (toggleSpacer != null) { if (Stories.isFeatureEnabled()) { StoryDisplay storyDisplay = StoryDisplay.Companion.getStoryDisplay(getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraButtonView.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraButtonView.java index 89801168c..54790a7f1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraButtonView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraButtonView.java @@ -3,6 +3,7 @@ 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; @@ -17,6 +18,7 @@ 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.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.ViewUtil; @@ -25,19 +27,20 @@ public class CameraButtonView extends View { private enum CameraButtonMode { IMAGE, MIXED } - private static final int CAPTURE_ARC_STROKE_WIDTH = 4; - private static final int HALF_CAPTURE_ARC_STROKE_WIDTH = CAPTURE_ARC_STROKE_WIDTH / 2; + 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 static final int DRAG_DISTANCE_MULTIPLIER = 3; private static final Interpolator ZOOM_INTERPOLATOR = new DecelerateInterpolator(); - 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 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; @@ -50,8 +53,8 @@ public class CameraButtonView extends View { private final float imageCaptureSize; private final float recordSize; - private final RectF progressRect = new RectF(); - private final Rect deadzoneRect = new Rect(); + private final RectF progressRect = new RectF(); + private final Rect deadzoneRect = new Rect(); private final @NonNull OnLongClickListener internalLongClickListener = v -> { notifyVideoCaptureStarted(); @@ -112,10 +115,19 @@ public class CameraButtonView extends View { arcPaint.setColor(0xFFFFFFFF); arcPaint.setAntiAlias(true); arcPaint.setStyle(Paint.Style.STROKE); - arcPaint.setStrokeWidth(ViewUtil.dpToPx(CAPTURE_ARC_STROKE_WIDTH)); + 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); @@ -153,8 +165,8 @@ public class CameraButtonView extends View { float radius = imageCaptureSize / 2f; canvas.drawCircle(centerX, centerY, radius, backgroundPaint); - canvas.drawCircle(centerX, centerY, radius, outlinePaint); - canvas.drawCircle(centerX, centerY, radius - ViewUtil.dpToPx(HALF_CAPTURE_ARC_STROKE_WIDTH), arcPaint); + canvas.drawCircle(centerX, centerY, radius, arcPaint); + canvas.drawCircle(centerX, centerY, radius - DimensionUnit.DP.toPixels(CAPTURE_FILL_PROTECTION), captureFillPaint); } private void drawForVideoCapture(Canvas canvas) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java index 824c9d80f..919b21645 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -32,10 +32,12 @@ import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; import androidx.camera.view.SignalCameraView; import androidx.core.content.ContextCompat; +import androidx.lifecycle.Observer; import com.bumptech.glide.Glide; import com.bumptech.glide.util.Executors; +import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.LoggingFragment; import org.thoughtcrime.securesms.R; @@ -49,11 +51,9 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.MediaConstraints; import org.thoughtcrime.securesms.stories.Stories; import org.thoughtcrime.securesms.stories.viewer.page.StoryDisplay; -import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.MemoryFileDescriptor; import org.thoughtcrime.securesms.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.signal.core.util.concurrent.SimpleTask; import org.thoughtcrime.securesms.video.VideoUtil; import java.io.FileDescriptor; @@ -76,6 +76,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { private View selfieFlash; private MemoryFileDescriptor videoFileDescriptor; + private final Observer> thumbObserver = this::presentRecentItemThumbnail; + private boolean isThumbAvailable; + private boolean isMediaSelected; + public static CameraXFragment newInstanceForAvatarCapture() { CameraXFragment fragment = new CameraXFragment(); Bundle args = new Bundle(); @@ -129,8 +133,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { onOrientationChanged(getResources().getConfiguration().orientation); - controller.getMostRecentMediaItem().observe(getViewLifecycleOwner(), this::presentRecentItemThumbnail); - view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { // Let's assume portrait for now, so 9:16 float aspectRatio = CameraFragment.getAspectRatioForOrientation(getResources().getConfiguration().orientation); @@ -162,6 +164,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { @Override public void onDestroyView() { super.onDestroyView(); + controller.getMostRecentMediaItem().removeObserver(thumbObserver); closeVideoFileDescriptor(); requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } @@ -223,6 +226,8 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { private void presentRecentItemThumbnail(Optional media) { if (media == null) { + isThumbAvailable = false; + updateGalleryVisibility(); return; } @@ -238,6 +243,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { thumbnail.setVisibility(View.GONE); thumbnail.setImageResource(0); } + + isThumbAvailable = media.isPresent(); + updateGalleryVisibility(); } @Override @@ -250,6 +258,19 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } else { countButton.setVisibility(View.GONE); } + + isMediaSelected = selectedMediaCount > 0; + updateGalleryVisibility(); + } + + private void updateGalleryVisibility() { + View cameraGalleryContainer = controlsContainer.findViewById(R.id.camera_gallery_button_background); + + if (isMediaSelected || !isThumbAvailable) { + cameraGalleryContainer.setVisibility(View.GONE); + } else { + cameraGalleryContainer.setVisibility(View.VISIBLE); + } } @SuppressLint({"ClickableViewAccessibility", "MissingPermission"}) @@ -274,6 +295,9 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } } + controller.getMostRecentMediaItem().removeObserver(thumbObserver); + controller.getMostRecentMediaItem().observeForever(thumbObserver); + selfieFlash = requireView().findViewById(R.id.camera_selfie_flash); captureButton.setOnClickListener(v -> { diff --git a/app/src/main/res/drawable/ic_switch_camera_28.xml b/app/src/main/res/drawable/ic_switch_camera_28.xml index ae216a9ff..5d861fbcb 100644 --- a/app/src/main/res/drawable/ic_switch_camera_28.xml +++ b/app/src/main/res/drawable/ic_switch_camera_28.xml @@ -5,8 +5,5 @@ android:viewportHeight="28"> - + android:pathData="M8.3,13.2l-3.5,3.5c-0.3,0.3 -0.8,0.3 -1.1,0l-3.5,-3.5l1.1,-1.1l1.6,1.6c0,0 0.3,0.4 0.7,1v-1.2l0,0c0.2,-5.8 5.1,-10.3 10.9,-10c2.6,0.1 5.1,1.2 7,3.1l-1.1,1.1c-3.5,-3.5 -9.2,-3.5 -12.7,0C5.9,9.3 5,11.6 5,14v0.7c0.3,-0.5 0.7,-1 0.7,-1l1.6,-1.6L8.3,13.2zM24.3,11.2c-0.3,-0.3 -0.8,-0.3 -1.1,0l-3.5,3.5l1.1,1.1l1.6,-1.6c0,0 0.3,-0.4 0.7,-1V14c0,5 -4,9 -9,9c-2.4,0 -4.7,-1 -6.4,-2.7l-1.1,1.1c4.1,4.1 10.7,4.1 14.8,0c1.9,-1.8 2.9,-4.3 3.1,-6.9l0,0v-1.2c0.4,0.5 0.7,1 0.7,1l1.6,1.6l1.1,-1.1L24.3,11.2z"/> diff --git a/app/src/main/res/drawable/media_selection_camera_switch_background.xml b/app/src/main/res/drawable/media_selection_camera_switch_background.xml new file mode 100644 index 000000000..83078aa7d --- /dev/null +++ b/app/src/main/res/drawable/media_selection_camera_switch_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/v2_media_count_indicator_background.xml b/app/src/main/res/drawable/v2_media_count_indicator_background.xml index 13c3127dc..7078e11dc 100644 --- a/app/src/main/res/drawable/v2_media_count_indicator_background.xml +++ b/app/src/main/res/drawable/v2_media_count_indicator_background.xml @@ -1,5 +1,5 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/layout/camera_controls_landscape.xml b/app/src/main/res/layout/camera_controls_landscape.xml index 65aa78a2e..1d1979a89 100644 --- a/app/src/main/res/layout/camera_controls_landscape.xml +++ b/app/src/main/res/layout/camera_controls_landscape.xml @@ -9,7 +9,7 @@ android:id="@+id/camera_capture_button" android:layout_width="96dp" android:layout_height="96dp" - android:layout_marginEnd="25dp" + android:layout_marginEnd="18dp" android:contentDescription="@string/CameraXFragment_capture_description" app:imageCaptureSize="60dp" app:layout_constraintBottom_toBottomOf="parent" @@ -26,47 +26,56 @@ android:background="@drawable/circle_transparent_black_40" android:padding="6dp" android:src="@drawable/camerax_flash_toggle" - app:layout_constraintStart_toEndOf="@+id/camera_flip_button" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - + + + + + diff --git a/app/src/main/res/layout/camera_controls_portrait.xml b/app/src/main/res/layout/camera_controls_portrait.xml index 1fc249235..22a3f93ba 100644 --- a/app/src/main/res/layout/camera_controls_portrait.xml +++ b/app/src/main/res/layout/camera_controls_portrait.xml @@ -7,11 +7,10 @@ - + + + + + + app:srcCompat="@drawable/ic_chevron_end_24" /> \ No newline at end of file diff --git a/app/src/main/res/values-night/material3_colors.xml b/app/src/main/res/values-night/material3_colors.xml index fe3476881..24a9a43b2 100644 --- a/app/src/main/res/values-night/material3_colors.xml +++ b/app/src/main/res/values-night/material3_colors.xml @@ -46,6 +46,7 @@ #1F414659 #991B1C1F + #61303133 #1FE2E1E5 #99BEBFC5 #99E2E1E5 diff --git a/app/src/main/res/values/material3_colors.xml b/app/src/main/res/values/material3_colors.xml index 07e25fee2..44c489fb6 100644 --- a/app/src/main/res/values/material3_colors.xml +++ b/app/src/main/res/values/material3_colors.xml @@ -46,6 +46,7 @@ #1FDCE5F9 #99FBFCFF + #61E7EBF3 #1F1B1B1D #99545863 #991B1D1D