Update camera UX to match Material3 Spec.

This commit is contained in:
Alex Hart
2022-06-20 16:18:44 -03:00
committed by Cody Henthorne
parent d30714bfd4
commit dc66583ef1
11 changed files with 182 additions and 91 deletions

View File

@@ -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);

View File

@@ -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) {

View File

@@ -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 -> {