mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-26 04:33:36 +00:00
Update camera UX to match Material3 Spec.
This commit is contained in:
committed by
Cody Henthorne
parent
d30714bfd4
commit
dc66583ef1
@@ -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<Stage> orderEnforcer;
|
||||
private Camera1Controller.Properties properties;
|
||||
|
||||
private final Observer<Optional<Media>> 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> 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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<Optional<Media>> 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> 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 -> {
|
||||
|
||||
Reference in New Issue
Block a user