diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31ca4d9ddc..766f10a655 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -554,7 +554,7 @@ + android:theme="@style/TextSecure.DarkNoActionBar" /> { // Let's assume portrait for now, so 9:16 - float aspectRatio = CameraFragment.getAspectRatioForOrientation(getResources().getConfiguration().orientation); + float aspectRatio = CameraFragment.getAspectRatioForOrientation(Configuration.ORIENTATION_PORTRAIT); float width = right - left; float height = Math.min((1f / aspectRatio) * width, bottom - top); @@ -231,12 +237,6 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, }); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - onOrientationChanged(newConfig.orientation); - } - @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { Log.d(TAG, "onSurfaceTextureAvailable"); @@ -322,25 +322,13 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, View galleryButton = requireView().findViewById(R.id.camera_gallery_button); View countButton = requireView().findViewById(R.id.camera_review_button); - View toggleSpacer = requireView().findViewById(R.id.toggle_spacer); mostRecentItemDisposable.dispose(); mostRecentItemDisposable = controller.getMostRecentMediaItem() .observeOn(AndroidSchedulers.mainThread()) .subscribe(item -> presentRecentItemThumbnail(item.orElse(null))); - if (toggleSpacer != null) { - if (Stories.isFeatureEnabled()) { - StoryDisplay storyDisplay = StoryDisplay.Companion.getStoryDisplay(getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels); - if (storyDisplay == StoryDisplay.SMALL) { - toggleSpacer.setVisibility(View.VISIBLE); - } else { - toggleSpacer.setVisibility(View.GONE); - } - } else { - toggleSpacer.setVisibility(View.GONE); - } - } + initializeViewFinderAndControlsPositioning(); captureButton.setOnClickListener(v -> { captureButton.setEnabled(false); @@ -368,6 +356,27 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, countButton.setOnClickListener(v -> controller.onCameraCountButtonClicked()); } + private void initializeViewFinderAndControlsPositioning() { + CardView cameraCard = requireView().findViewById(R.id.camera_preview_parent); + View controls = requireView().findViewById(R.id.camera_controls_container); + CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireView()); + + if (!cameraDisplay.getRoundViewFinderCorners()) { + cameraCard.setRadius(0f); + } + + ViewUtil.setBottomMargin(controls, cameraDisplay.getCameraCaptureMarginBottom(getResources())); + + if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone((ConstraintLayout) requireView()); + constraintSet.connect(R.id.camera_preview_parent, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); + constraintSet.applyTo((ConstraintLayout) requireView()); + } else { + ViewUtil.setBottomMargin(cameraCard, cameraDisplay.getCameraViewportMarginBottom()); + } + } + private void onCaptureClicked() { orderEnforcer.reset(); @@ -426,9 +435,8 @@ public class Camera1Fragment extends LoggingFragment implements CameraFragment, return new PointF(scaleX, scaleY); } - private void onOrientationChanged(int orientation) { - int layout = orientation == Configuration.ORIENTATION_PORTRAIT ? R.layout.camera_controls_portrait - : R.layout.camera_controls_landscape; + private void onOrientationChanged() { + int layout = R.layout.camera_controls_portrait; controlsContainer.removeAllViews(); controlsContainer.addView(LayoutInflater.from(getContext()).inflate(layout, controlsContainer, false)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraDisplay.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraDisplay.kt new file mode 100644 index 0000000000..28352c0049 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraDisplay.kt @@ -0,0 +1,139 @@ +package org.thoughtcrime.securesms.mediasend + +import android.content.res.Resources +import android.view.View +import androidx.annotation.Dimension +import androidx.annotation.Px +import androidx.window.WindowManager +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. + */ +enum class CameraDisplay( + private val aspectRatio: Float, + val roundViewFinderCorners: Boolean, + private val withTogglePositionInfo: PositionInfo, + private val withoutTogglePositionInfo: PositionInfo, + @Dimension(unit = Dimension.DP) private val toggleBottomMargin: Int +) { + DISPLAY_20_9( + aspectRatio = 9f / 20f, + roundViewFinderCorners = true, + withTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 130, + cameraViewportMarginBottomDp = 106, + cameraViewportGravity = CameraViewportGravity.BOTTOM + ), + withoutTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 130, + cameraViewportGravity = CameraViewportGravity.CENTER + ), + toggleBottomMargin = 52 + ), + DISPLAY_19_9( + aspectRatio = 9f / 19f, + roundViewFinderCorners = true, + withTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 128, + cameraViewportMarginBottomDp = 104, + cameraViewportGravity = CameraViewportGravity.BOTTOM + ), + withoutTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 128, + cameraViewportGravity = CameraViewportGravity.CENTER + ), + toggleBottomMargin = 52 + ), + DISPLAY_18_9( + aspectRatio = 9f / 18f, + roundViewFinderCorners = true, + withTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 120, + cameraViewportGravity = CameraViewportGravity.CENTER + ), + withoutTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 84, + cameraViewportGravity = CameraViewportGravity.CENTER + ), + toggleBottomMargin = 54 + ), + DISPLAY_16_9( + aspectRatio = 9f / 16f, + roundViewFinderCorners = false, + withTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 120, + cameraViewportGravity = CameraViewportGravity.BOTTOM + ), + withoutTogglePositionInfo = PositionInfo( + cameraCaptureMarginBottomDp = 84, + cameraViewportGravity = CameraViewportGravity.BOTTOM + ), + toggleBottomMargin = 54 + ); + + @Px + fun getCameraCaptureMarginBottom(resources: Resources): Int { + val positionInfo = if (Stories.isFeatureEnabled()) withTogglePositionInfo else withoutTogglePositionInfo + + return positionInfo.cameraCaptureMarginBottomDp.dp - getCameraButtonSizeOffset(resources) + } + + @Px + fun getCameraViewportMarginBottom(): Int { + val positionInfo = if (Stories.isFeatureEnabled()) withTogglePositionInfo else withoutTogglePositionInfo + + return positionInfo.cameraViewportMarginBottomDp.dp + } + + fun getCameraViewportGravity(): CameraViewportGravity { + val positionInfo = if (Stories.isFeatureEnabled()) withTogglePositionInfo else withoutTogglePositionInfo + + return positionInfo.cameraViewportGravity + } + + @Px + fun getToggleBottomMargin(): Int { + return toggleBottomMargin.dp + } + + companion object { + @Px + @JvmStatic + private fun getCameraButtonSizeOffset(resources: Resources): Int { + val cameraCaptureButtonSize = resources.getDimensionPixelSize(R.dimen.camera_capture_button_size) + val cameraCaptureImageButtonSize = resources.getDimensionPixelSize(R.dimen.camera_capture_image_button_size) + + return (cameraCaptureButtonSize - cameraCaptureImageButtonSize) / 2 + } + + @JvmStatic + fun getDisplay(view: View): CameraDisplay { + val windowManager = WindowManager(view.context) + val windowMetrics = windowManager.getCurrentWindowMetrics() + val width = windowMetrics.bounds.width() + val height = windowMetrics.bounds.height() + val aspectRatio = width.toFloat() / height + + return when { + aspectRatio <= DISPLAY_20_9.aspectRatio -> DISPLAY_20_9 + aspectRatio <= DISPLAY_19_9.aspectRatio -> DISPLAY_19_9 + aspectRatio <= DISPLAY_18_9.aspectRatio -> DISPLAY_18_9 + else -> DISPLAY_16_9 + } + } + } + + enum class CameraViewportGravity { + CENTER, + BOTTOM + } + + data class PositionInfo( + @Dimension(unit = Dimension.DP) val cameraCaptureMarginBottomDp: Int, + @Dimension(unit = Dimension.DP) val cameraViewportMarginBottomDp: Int = 0, + val cameraViewportGravity: CameraViewportGravity + ) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java index e443f12871..302f9c5828 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraFragment.java @@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.mediasend; import android.annotation.SuppressLint; import android.content.res.Configuration; +import android.view.Window; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.camera.view.video.ExperimentalVideo; 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 208a725b38..f047e93431 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraXFragment.java @@ -25,21 +25,23 @@ import android.widget.ImageView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; -import androidx.camera.core.AspectRatio; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageCapture; import androidx.camera.core.ImageCaptureException; import androidx.camera.core.ImageProxy; -import androidx.camera.core.Preview; import androidx.camera.view.CameraController; import androidx.camera.view.LifecycleCameraController; import androidx.camera.view.PreviewView; import androidx.camera.view.video.ExperimentalVideo; +import androidx.cardview.widget.CardView; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.constraintlayout.widget.ConstraintSet; import androidx.core.content.ContextCompat; import com.bumptech.glide.Glide; import com.bumptech.glide.util.Executors; +import org.signal.core.util.Stopwatch; import org.signal.core.util.concurrent.SimpleTask; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.LoggingFragment; @@ -52,11 +54,9 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaAnimations; import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton; 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.MemoryFileDescriptor; -import org.signal.core.util.Stopwatch; import org.thoughtcrime.securesms.util.TextSecurePreferences; +import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.video.VideoUtil; import java.io.FileDescriptor; @@ -147,11 +147,11 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { previewView.setScaleType(PREVIEW_SCALE_TYPE); previewView.setController(cameraController); - onOrientationChanged(getResources().getConfiguration().orientation); + onOrientationChanged(); 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); + float aspectRatio = CameraFragment.getAspectRatioForOrientation(Configuration.ORIENTATION_PORTRAIT); float width = right - left; float height = Math.min((1f / aspectRatio) * width, bottom - top); @@ -176,6 +176,11 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); } + @Override + public void onPause() { + super.onPause(); + } + @Override public void onDestroyView() { super.onDestroyView(); @@ -184,12 +189,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); } - @Override - public void onConfigurationChanged(@NonNull Configuration newConfig) { - super.onConfigurationChanged(newConfig); - onOrientationChanged(newConfig.orientation); - } - @Override public void fadeOutControls(@NonNull Runnable onEndAction) { controlsContainer.setEnabled(false); @@ -221,12 +220,11 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { }); } - private void onOrientationChanged(int orientation) { - int layout = orientation == Configuration.ORIENTATION_PORTRAIT ? R.layout.camera_controls_portrait - : R.layout.camera_controls_landscape; + private void onOrientationChanged() { + int layout = R.layout.camera_controls_portrait; - int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels); - Size size = CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, orientation == Configuration.ORIENTATION_PORTRAIT); + int resolution = CameraXUtil.getIdealResolution(Resources.getSystem().getDisplayMetrics().widthPixels, Resources.getSystem().getDisplayMetrics().heightPixels); + Size size = CameraXUtil.buildResolutionForRatio(resolution, ASPECT_RATIO_16_9, true); CameraController.OutputSize outputSize = new CameraController.OutputSize(size); cameraController.setImageCaptureTargetSize(outputSize); @@ -280,28 +278,37 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } } - @SuppressLint({"ClickableViewAccessibility", "MissingPermission"}) - private void initControls() { - View flipButton = requireView().findViewById(R.id.camera_flip_button); - CameraButtonView captureButton = requireView().findViewById(R.id.camera_capture_button); - View galleryButton = requireView().findViewById(R.id.camera_gallery_button); - View countButton = requireView().findViewById(R.id.camera_review_button); - CameraXFlashToggleView flashButton = requireView().findViewById(R.id.camera_flash_button); - View toggleSpacer = requireView().findViewById(R.id.toggle_spacer); + private void initializeViewFinderAndControlsPositioning() { + CardView cameraCard = requireView().findViewById(R.id.camerax_camera_parent); + View controls = requireView().findViewById(R.id.camerax_controls_container); + CameraDisplay cameraDisplay = CameraDisplay.getDisplay(requireView()); - if (toggleSpacer != null) { - if ( Stories.isFeatureEnabled()) { - StoryDisplay storyDisplay = StoryDisplay.Companion.getStoryDisplay(getResources().getDisplayMetrics().widthPixels, getResources().getDisplayMetrics().heightPixels); - if (storyDisplay == StoryDisplay.SMALL) { - toggleSpacer.setVisibility(View.VISIBLE); - } else { - toggleSpacer.setVisibility(View.GONE); - } - } else { - toggleSpacer.setVisibility(View.GONE); - } + if (!cameraDisplay.getRoundViewFinderCorners()) { + cameraCard.setRadius(0f); } + ViewUtil.setBottomMargin(controls, cameraDisplay.getCameraCaptureMarginBottom(getResources())); + + if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) { + ConstraintSet constraintSet = new ConstraintSet(); + constraintSet.clone((ConstraintLayout) requireView()); + constraintSet.connect(R.id.camerax_camera_parent, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP); + constraintSet.applyTo((ConstraintLayout) requireView()); + } else { + ViewUtil.setBottomMargin(cameraCard, cameraDisplay.getCameraViewportMarginBottom()); + } + } + + @SuppressLint({ "ClickableViewAccessibility", "MissingPermission" }) + private void initControls() { + View flipButton = requireView().findViewById(R.id.camera_flip_button); + CameraButtonView captureButton = requireView().findViewById(R.id.camera_capture_button); + View galleryButton = requireView().findViewById(R.id.camera_gallery_button); + View countButton = requireView().findViewById(R.id.camera_review_button); + CameraXFlashToggleView flashButton = requireView().findViewById(R.id.camera_flash_button); + + initializeViewFinderAndControlsPositioning(); + mostRecentItemDisposable.dispose(); mostRecentItemDisposable = controller.getMostRecentMediaItem() .observeOn(AndroidSchedulers.mainThread()) @@ -392,10 +399,10 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } private boolean isVideoRecordingSupported(@NonNull Context context) { - return Build.VERSION.SDK_INT >= 26 && + return Build.VERSION.SDK_INT >= 26 && requireArguments().getBoolean(IS_VIDEO_ENABLED, true) && - MediaConstraints.isVideoTranscodeAvailable() && - CameraXUtil.isMixedModeSupported(context) && + MediaConstraints.isVideoTranscodeAvailable() && + CameraXUtil.isMixedModeSupported(context) && VideoUtil.getMaxVideoRecordDurationInSeconds(context, controller.getMediaConstraints()) > 0; } @@ -509,7 +516,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { } } - @SuppressLint({"MissingPermission"}) + @SuppressLint({ "MissingPermission" }) private void initializeFlipButton(@NonNull View flipButton, @NonNull CameraXFlashToggleView flashButton) { if (getContext() == null) { Log.w(TAG, "initializeFlipButton called either before or after fragment was attached."); @@ -518,7 +525,7 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment { if (cameraController.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) && cameraController.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA)) { flipButton.setVisibility(View.VISIBLE); - flipButton.setOnClickListener(v -> { + flipButton.setOnClickListener(v -> { cameraController.setCameraSelector(cameraController.getCameraSelector() == CameraSelector.DEFAULT_FRONT_CAMERA ? CameraSelector.DEFAULT_BACK_CAMERA : CameraSelector.DEFAULT_FRONT_CAMERA); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt index 614dd38305..63f6300914 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionActivity.kt @@ -6,6 +6,8 @@ import android.content.Intent import android.graphics.Color import android.os.Bundle import android.view.KeyEvent +import android.view.WindowManager +import android.widget.FrameLayout import android.widget.TextView import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels @@ -13,6 +15,7 @@ import androidx.appcompat.app.AppCompatDelegate import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.core.content.ContextCompat +import androidx.core.view.updateLayoutParams import androidx.lifecycle.ViewModelProvider import androidx.navigation.Navigation import androidx.navigation.fragment.NavHostFragment @@ -29,6 +32,7 @@ import org.thoughtcrime.securesms.conversation.MessageSendType 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.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivityResult import org.thoughtcrime.securesms.mediasend.v2.review.MediaReviewFragment @@ -80,6 +84,10 @@ class MediaSelectionActivity : override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { setContentView(R.layout.media_selection_activity) + window.addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + ) + val sendType: MessageSendType = requireNotNull(intent.getParcelableExtra(MESSAGE_SEND_TYPE)) val initialMedia: List = intent.getParcelableArrayListExtra(MEDIA) ?: listOf() val message: CharSequence? = if (shareToTextStory) null else draftText @@ -89,6 +97,12 @@ class MediaSelectionActivity : viewModel = ViewModelProvider(this, factory)[MediaSelectionViewModel::class.java] val textStoryToggle: ConstraintLayout = findViewById(R.id.switch_widget) + val cameraDisplay = CameraDisplay.getDisplay(textStoryToggle) + + textStoryToggle.updateLayoutParams { + bottomMargin = cameraDisplay.getToggleBottomMargin() + } + val cameraSelectedConstraintSet = ConstraintSet().apply { clone(textStoryToggle) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt index 2e397a88e5..21ab20f441 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/gallery/MediaGalleryFragment.kt @@ -3,20 +3,20 @@ package org.thoughtcrime.securesms.mediasend.v2.gallery import android.os.Bundle import android.view.View import androidx.activity.OnBackPressedCallback -import androidx.appcompat.widget.Toolbar +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.ItemTouchHelper -import androidx.recyclerview.widget.RecyclerView import org.signal.core.util.Stopwatch import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.recyclerview.GridDividerDecoration +import org.thoughtcrime.securesms.databinding.V2MediaGalleryFragmentBinding import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaRepository -import org.thoughtcrime.securesms.mediasend.v2.MediaCountIndicatorButton import org.thoughtcrime.securesms.util.Material3OnScrollHelper import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter @@ -36,12 +36,6 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) { private lateinit var callbacks: Callbacks - private lateinit var toolbar: Toolbar - private lateinit var galleryRecycler: RecyclerView - private lateinit var countButton: MediaCountIndicatorButton - private lateinit var bottomBarGroup: View - private lateinit var selectedRecycler: RecyclerView - private var selectedMediaTouchHelper: ItemTouchHelper? = null private val galleryAdapter = MappingAdapter() @@ -57,29 +51,39 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { callbacks = requireListener() + val binding = V2MediaGalleryFragmentBinding.bind(view) - toolbar = view.findViewById(R.id.media_gallery_toolbar) - galleryRecycler = view.findViewById(R.id.media_gallery_grid) - selectedRecycler = view.findViewById(R.id.media_gallery_selected) - countButton = view.findViewById(R.id.media_gallery_count_button) - bottomBarGroup = view.findViewById(R.id.media_gallery_bottom_bar_group) + binding.root.setPadding( + 0, + 0, + 0, + ViewUtil.getNavigationBarHeight(view) + ) - (galleryRecycler.layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { + binding.mediaGalleryToolbar.updateLayoutParams { + topMargin = ViewUtil.getStatusBarHeight(view) + } + + binding.mediaGalleryStatusBarBackground.updateLayoutParams { + height = ViewUtil.getStatusBarHeight(view) + } + + (binding.mediaGalleryGrid.layoutManager as GridLayoutManager).spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { override fun getSpanSize(position: Int): Int { - val isFolder: Boolean = (galleryRecycler.adapter as MappingAdapter).getModel(position).map { it is MediaGallerySelectableItem.FolderModel }.orElse(false) + val isFolder: Boolean = (binding.mediaGalleryGrid.adapter as MappingAdapter).getModel(position).map { it is MediaGallerySelectableItem.FolderModel }.orElse(false) return if (isFolder) 2 else 1 } } - toolbar.setNavigationOnClickListener { + binding.mediaGalleryToolbar.setNavigationOnClickListener { onBack() } - Material3OnScrollHelper(requireActivity(), toolbar).attach(galleryRecycler) + Material3OnScrollHelper(requireActivity(), listOf(binding.mediaGalleryToolbar, binding.mediaGalleryStatusBarBackground)).attach(binding.mediaGalleryGrid) if (callbacks.isCameraEnabled()) { - toolbar.setOnMenuItemClickListener { item -> + binding.mediaGalleryToolbar.setOnMenuItemClickListener { item -> if (item.itemId == R.id.action_camera) { callbacks.onNavigateToCamera() true @@ -88,18 +92,18 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) { } } } else { - toolbar.menu.findItem(R.id.action_camera).isVisible = false + binding.mediaGalleryToolbar.menu.findItem(R.id.action_camera).isVisible = false } - countButton.setOnClickListener { + binding.mediaGalleryCountButton.setOnClickListener { callbacks.onSubmit() } MediaGallerySelectedItem.register(selectedAdapter) { media -> callbacks.onSelectedMediaClicked(media) } - selectedRecycler.adapter = selectedAdapter - selectedMediaTouchHelper?.attachToRecyclerView(selectedRecycler) + binding.mediaGallerySelected.adapter = selectedAdapter + selectedMediaTouchHelper?.attachToRecyclerView(binding.mediaGallerySelected) MediaGallerySelectableItem.registerAdapter( mappingAdapter = galleryAdapter, @@ -117,25 +121,25 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) { callbacks.isMultiselectEnabled() ) - galleryRecycler.adapter = galleryAdapter - galleryRecycler.addItemDecoration(GridDividerDecoration(4, ViewUtil.dpToPx(2))) + binding.mediaGalleryGrid.adapter = galleryAdapter + binding.mediaGalleryGrid.addItemDecoration(GridDividerDecoration(4, ViewUtil.dpToPx(2))) viewStateLiveData.observe(viewLifecycleOwner) { state -> - bottomBarGroup.visible = state.selectedMedia.isNotEmpty() - countButton.setCount(state.selectedMedia.size) + binding.mediaGalleryBottomBarGroup.visible = state.selectedMedia.isNotEmpty() + binding.mediaGalleryCountButton.setCount(state.selectedMedia.size) val stopwatch = Stopwatch("mediaSubmit") selectedAdapter.submitList(state.selectedMedia.map { MediaGallerySelectedItem.Model(it) }) { stopwatch.split("after-submit") stopwatch.stop("MediaGalleryFragment") if (state.selectedMedia.isNotEmpty()) { - selectedRecycler.smoothScrollToPosition(state.selectedMedia.size - 1) + binding.mediaGallerySelected.smoothScrollToPosition(state.selectedMedia.size - 1) } } } viewModel.state.observe(viewLifecycleOwner) { state -> - toolbar.title = state.bucketTitle ?: requireContext().getString(R.string.AttachmentKeyboard_gallery) + binding.mediaGalleryToolbar.title = state.bucketTitle ?: requireContext().getString(R.string.AttachmentKeyboard_gallery) } val galleryItemsWithSelection = LiveDataUtil.combineLatest( diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt index ee3815afa9..ca7a9a12b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/review/MediaReviewFragment.kt @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.MediaUtil +import org.thoughtcrime.securesms.util.ViewUtil import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter import org.thoughtcrime.securesms.util.fragments.requireListener import org.thoughtcrime.securesms.util.views.TouchInterceptingFrameLayout @@ -90,6 +91,13 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) { callback = requireListener() + view.setPadding( + 0, + ViewUtil.getStatusBarHeight(view), + 0, + ViewUtil.getNavigationBarHeight(view) + ) + drawToolButton = view.findViewById(R.id.draw_tool) cropAndRotateButton = view.findViewById(R.id.crop_and_rotate_tool) qualityButton = view.findViewById(R.id.quality_selector) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt index 862cee4cad..84f484faba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/text/TextStoryPostCreationFragment.kt @@ -4,10 +4,11 @@ import android.content.pm.ActivityInfo import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.appcompat.widget.AppCompatImageView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.core.view.drawToBitmap import androidx.core.view.postDelayed +import androidx.core.view.updateLayoutParams import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.navigation.fragment.findNavController @@ -15,8 +16,10 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.contacts.paged.ContactSearchKey import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectForwardFragmentArgs +import org.thoughtcrime.securesms.databinding.StoriesTextPostCreationFragmentBinding import org.thoughtcrime.securesms.linkpreview.LinkPreviewRepository import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel +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 @@ -24,18 +27,14 @@ import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendReposi import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendResult import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet import org.thoughtcrime.securesms.stories.Stories -import org.thoughtcrime.securesms.stories.StoryTextPostView import org.thoughtcrime.securesms.util.LifecycleDisposable import org.thoughtcrime.securesms.util.livedata.LiveDataUtil import org.thoughtcrime.securesms.util.visible class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creation_fragment), TextStoryPostTextEntryFragment.Callback, SafetyNumberBottomSheet.Callbacks { - private lateinit var scene: ConstraintLayout - private lateinit var backgroundButton: AppCompatImageView - private lateinit var send: View - private lateinit var storyTextPostView: StoryTextPostView - private lateinit var sendInProgressCard: View + private var _binding: StoriesTextPostCreationFragmentBinding? = null + private val binding: StoriesTextPostCreationFragmentBinding get() = _binding!! private val sharedViewModel: MediaSelectionViewModel by viewModels( ownerProducer = { @@ -66,16 +65,9 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - scene = view.findViewById(R.id.scene) - backgroundButton = view.findViewById(R.id.background_selector) - send = view.findViewById(R.id.send) - storyTextPostView = view.findViewById(R.id.story_text_post) - sendInProgressCard = view.findViewById(R.id.send_in_progress_indicator) + _binding = StoriesTextPostCreationFragmentBinding.bind(view) - val backgroundProtection: View = view.findViewById(R.id.background_protection) - val addLinkProtection: View = view.findViewById(R.id.add_link_protection) - - storyTextPostView.showCloseButton() + binding.storyTextPost.showCloseButton() lifecycleDisposable.bindTo(viewLifecycleOwner) lifecycleDisposable += sharedViewModel.hudCommands.subscribe { @@ -85,12 +77,12 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati } viewModel.typeface.observe(viewLifecycleOwner) { typeface -> - storyTextPostView.setTypeface(typeface) + binding.storyTextPost.setTypeface(typeface) } viewModel.state.observe(viewLifecycleOwner) { state -> - backgroundButton.background = state.backgroundColor.chatBubbleMask - storyTextPostView.bindFromCreationState(state) + binding.backgroundSelector.background = state.backgroundColor.chatBubbleMask + binding.storyTextPost.bindFromCreationState(state) if (state.linkPreviewUri != null) { linkPreviewViewModel.onTextChanged(requireContext(), state.linkPreviewUri, 0, state.linkPreviewUri.lastIndex) @@ -99,32 +91,32 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati } val canSend = state.body.isNotEmpty() || !state.linkPreviewUri.isNullOrEmpty() - send.alpha = if (canSend) 1f else 0.5f - send.isEnabled = canSend + binding.send.alpha = if (canSend) 1f else 0.5f + binding.send.isEnabled = canSend } LiveDataUtil.combineLatest(viewModel.state, linkPreviewViewModel.linkPreviewState) { viewState, linkState -> Pair(viewState.body.isBlank(), linkState) }.observe(viewLifecycleOwner) { (useLargeThumb, linkState) -> - storyTextPostView.bindLinkPreviewState(linkState, View.GONE, useLargeThumb) - storyTextPostView.postAdjustLinkPreviewTranslationY() + binding.storyTextPost.bindLinkPreviewState(linkState, View.GONE, useLargeThumb) + binding.storyTextPost.postAdjustLinkPreviewTranslationY() } - storyTextPostView.setTextViewClickListener { - storyTextPostView.hidePostContent() - storyTextPostView.isEnabled = false + binding.storyTextPost.setTextViewClickListener { + binding.storyTextPost.hidePostContent() + binding.storyTextPost.isEnabled = false TextStoryPostTextEntryFragment().show(childFragmentManager, null) } - backgroundProtection.setOnClickListener { + binding.backgroundProtection.setOnClickListener { viewModel.cycleBackgroundColor() } - addLinkProtection.setOnClickListener { + binding.addLinkProtection.setOnClickListener { TextStoryPostLinkEntryFragment().show(childFragmentManager, null) } - storyTextPostView.setLinkPreviewCloseListener { + binding.storyTextPost.setLinkPreviewCloseListener { viewModel.setLinkPreview("") } @@ -132,23 +124,23 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati if (it.isNotEmpty()) { performSend(it.toSet()) } else { - send.isClickable = true - sendInProgressCard.visible = false + binding.send.isClickable = true + binding.sendInProgressIndicator.visible = false } } - send.setOnClickListener { - send.isClickable = false - sendInProgressCard.visible = true + binding.send.setOnClickListener { + binding.send.isClickable = false + binding.sendInProgressIndicator.visible = true - storyTextPostView.hideCloseButton() + binding.storyTextPost.hideCloseButton() val contacts = (sharedViewModel.destination.getRecipientSearchKeyList() + sharedViewModel.destination.getRecipientSearchKey()) .filterIsInstance(ContactSearchKey::class.java) .toSet() if (contacts.isEmpty()) { - val bitmap = storyTextPostView.drawToBitmap() + val bitmap = binding.storyTextPost.drawToBitmap() viewModel.compressToBlob(bitmap).observeOn(AndroidSchedulers.mainThread()).subscribe { uri -> launcher.launch( StoriesMultiselectForwardActivity.Args( @@ -166,18 +158,54 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati performSend(contacts) } } + + initializeScenePositioning() } override fun onResume() { super.onResume() - storyTextPostView.showCloseButton() + binding.storyTextPost.showCloseButton() requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } + override fun onDestroy() { + super.onDestroy() + _binding = null + } + override fun onTextStoryPostTextEntryDismissed() { - storyTextPostView.postDelayed(resources.getInteger(R.integer.text_entry_exit_duration).toLong()) { - storyTextPostView.showPostContent() - storyTextPostView.isEnabled = true + binding.storyTextPost.postDelayed(resources.getInteger(R.integer.text_entry_exit_duration).toLong()) { + binding.storyTextPost.showPostContent() + binding.storyTextPost.isEnabled = true + } + } + + private fun initializeScenePositioning() { + val cameraDisplay = CameraDisplay.getDisplay(requireView()) + + if (!cameraDisplay.roundViewFinderCorners) { + binding.storyTextPostCard.radius = 0f + } + + binding.send.updateLayoutParams { + bottomMargin = cameraDisplay.getToggleBottomMargin() + } + + listOf(binding.backgroundProtection, binding.addLinkProtection).forEach { + it.updateLayoutParams { + bottomMargin += cameraDisplay.getCameraCaptureMarginBottom(resources) + } + } + + if (cameraDisplay.getCameraViewportGravity() == CameraDisplay.CameraViewportGravity.CENTER) { + val constraintSet = ConstraintSet() + constraintSet.clone(binding.scene) + constraintSet.connect(R.id.story_text_post_card, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP) + constraintSet.applyTo(binding.scene) + } else { + binding.storyTextPostCard.updateLayoutParams { + bottomMargin = cameraDisplay.getCameraViewportMarginBottom() + } } } @@ -196,8 +224,8 @@ class TextStoryPostCreationFragment : Fragment(R.layout.stories_text_post_creati requireActivity().finish() } is TextStoryPostSendResult.UntrustedRecordsError -> { - send.isClickable = true - sendInProgressCard.visible = false + binding.send.isClickable = true + binding.sendInProgressIndicator.visible = false SafetyNumberBottomSheet .forIdentityRecordsAndDestinations(result.untrustedRecords, contacts.toList()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java index 26aaaaa45f..10e59e9e8c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/scribbles/ImageEditorFragment.java @@ -224,6 +224,15 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu Mode mode = Mode.getByCode(requireArguments().getString(KEY_MODE)); + if (mode == Mode.AVATAR_CAPTURE || mode == Mode.AVATAR_EDIT) { + view.setPadding( + 0, + ViewUtil.getStatusBarHeight(view), + 0, + ViewUtil.getNavigationBarHeight(view) + ); + } + imageEditorHud = view.findViewById(R.id.scribble_hud); imageEditorView = view.findViewById(R.id.image_editor_view); diff --git a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java index c112237c7b..27bf8674ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/wallpaper/crop/WallpaperImageSelectionActivity.java @@ -4,6 +4,7 @@ import android.Manifest; import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -40,6 +41,11 @@ public final class WallpaperImageSelectionActivity extends AppCompatActivity @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + getWindow().addFlags( + WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + ); + setContentView(R.layout.wallpaper_image_selection_activity); getSupportFragmentManager().beginTransaction() diff --git a/app/src/main/res/layout/camera_controls_landscape.xml b/app/src/main/res/layout/camera_controls_landscape.xml deleted file mode 100644 index b0bba5b1ff..0000000000 --- a/app/src/main/res/layout/camera_controls_landscape.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/camera_controls_portrait.xml b/app/src/main/res/layout/camera_controls_portrait.xml index 02696c0bda..6d751da660 100644 --- a/app/src/main/res/layout/camera_controls_portrait.xml +++ b/app/src/main/res/layout/camera_controls_portrait.xml @@ -8,11 +8,11 @@ @@ -40,7 +40,7 @@ android:contentDescription="@string/CameraXFragment_change_camera_description" android:scaleType="centerInside" android:visibility="gone" - app:layout_constraintBottom_toTopOf="@id/toggle_spacer" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:srcCompat="@drawable/ic_switch_camera_28" tools:visibility="visible" /> @@ -52,7 +52,7 @@ android:layout_marginEnd="40dp" android:layout_marginBottom="36dp" android:background="@drawable/circle_tintable" - app:layout_constraintBottom_toTopOf="@id/toggle_spacer" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"> - - diff --git a/app/src/main/res/layout/camera_fragment.xml b/app/src/main/res/layout/camera_fragment.xml index d05b9afb5b..fb1603914e 100644 --- a/app/src/main/res/layout/camera_fragment.xml +++ b/app/src/main/res/layout/camera_fragment.xml @@ -12,7 +12,7 @@ android:layout_height="wrap_content" app:cardCornerRadius="18dp" app:cardElevation="0dp" - app:layout_constraintTop_toTopOf="parent"> + app:layout_constraintBottom_toBottomOf="parent"> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="@id/camera_preview_parent" /> diff --git a/app/src/main/res/layout/camerax_fragment.xml b/app/src/main/res/layout/camerax_fragment.xml index 8d5ca0a943..1b9b6731e7 100644 --- a/app/src/main/res/layout/camerax_fragment.xml +++ b/app/src/main/res/layout/camerax_fragment.xml @@ -1,18 +1,18 @@ + android:layout_height="match_parent" + tools:viewBindingIgnore="true"> + app:layout_constraintBottom_toBottomOf="parent"> + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="@id/camerax_camera_parent" /> \ No newline at end of file diff --git a/app/src/main/res/layout/media_selection_activity.xml b/app/src/main/res/layout/media_selection_activity.xml index d3d0e1987d..bfaaa89de2 100644 --- a/app/src/main/res/layout/media_selection_activity.xml +++ b/app/src/main/res/layout/media_selection_activity.xml @@ -1,10 +1,10 @@ + android:layout_height="match_parent" + tools:viewBindingIgnore="true"> + android:layout_gravity="bottom|center_horizontal"> + app:layout_constraintStart_toStartOf="parent"> @@ -60,7 +56,7 @@ android:layout_marginBottom="6dp" android:padding="2dp" android:src="@drawable/circle_tintable" - app:layout_constraintBottom_toTopOf="@id/button_bar_barrier" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/background_protection" app:tint="@color/transparent_black_40" /> @@ -84,35 +80,12 @@ android:background="@color/signal_colorOnSecondaryContainer" android:padding="4dp" android:scaleType="centerInside" - app:layout_constraintBottom_toBottomOf="@id/toggle_spacer" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toTopOf="@id/toggle_spacer" app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Signal.Circle" app:srcCompat="@drawable/ic_arrow_end_24" app:tint="@color/signal_colorSecondaryContainer" /> - - - - - - + + 69dp 16dp + 124dp + 76dp 18dp 4dp