mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-21 00:59:49 +01:00
Material 3 media gallery refresh.
This commit is contained in:
committed by
Cody Henthorne
parent
b78633f9a7
commit
359a39ddaf
@@ -13,7 +13,6 @@ import android.view.MotionEvent;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.DecelerateInterpolator;
|
||||
@@ -156,8 +155,6 @@ public class CameraXFragment extends LoggingFragment implements CameraFragment {
|
||||
super.onResume();
|
||||
|
||||
camera.bindToLifecycle(getViewLifecycleOwner(), this::handleCameraInitializationError);
|
||||
requireActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||
requireActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
|
||||
requireActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.mediasend.v2.text.send.TextStoryPostSendReposi
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.safety.SafetyNumberBottomSheet
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
import org.thoughtcrime.securesms.util.navigation.safeNavigate
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
|
||||
@@ -155,6 +156,8 @@ class MediaSelectionActivity :
|
||||
}
|
||||
|
||||
onBackPressedDispatcher.addCallback(OnBackPressed())
|
||||
|
||||
FullscreenHelper.setLowProfileMode(window)
|
||||
}
|
||||
|
||||
private fun animateTextStyling(selectedSwitch: TextView, unselectedSwitch: TextView, duration: Long) {
|
||||
|
||||
@@ -132,7 +132,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
||||
}
|
||||
|
||||
viewModel.state.observe(viewLifecycleOwner) { state ->
|
||||
toolbar.title = state.bucketTitle
|
||||
toolbar.title = state.bucketTitle ?: requireContext().getString(R.string.AttachmentKeyboard_gallery)
|
||||
}
|
||||
|
||||
val galleryItemsWithSelection = LiveDataUtil.combineLatest(
|
||||
@@ -141,7 +141,7 @@ class MediaGalleryFragment : Fragment(R.layout.v2_media_gallery_fragment) {
|
||||
) { galleryItems, selectedMedia ->
|
||||
galleryItems.map {
|
||||
if (it is MediaGallerySelectableItem.FileModel) {
|
||||
it.copy(isSelected = selectedMedia.contains(it.media))
|
||||
it.copy(isSelected = selectedMedia.contains(it.media), selectionOneBasedIndex = selectedMedia.indexOf(it.media) + 1)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
package org.thoughtcrime.securesms.mediasend.v2.gallery
|
||||
|
||||
import android.animation.ValueAnimator
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.setPadding
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout
|
||||
import com.google.android.material.imageview.ShapeableImageView
|
||||
import org.signal.core.util.DimensionUnit
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
@@ -26,6 +29,8 @@ typealias OnMediaFolderClicked = (MediaFolder) -> Unit
|
||||
typealias OnMediaClicked = (Media, Boolean) -> Unit
|
||||
|
||||
private val FILE_VIEW_HOLDER_TAG = Log.tag(MediaGallerySelectableItem.FileViewHolder::class.java)
|
||||
private const val PAYLOAD_CHECK_CHANGED = 0
|
||||
private const val PAYLOAD_INDEX_CHANGED = 1
|
||||
|
||||
object MediaGallerySelectableItem {
|
||||
|
||||
@@ -51,14 +56,10 @@ object MediaGallerySelectableItem {
|
||||
}
|
||||
|
||||
abstract class BaseViewHolder<T : MappingModel<T>>(itemView: View) : MappingViewHolder<T>(itemView) {
|
||||
protected val imageView: ImageView = itemView.findViewById(R.id.media_gallery_image)
|
||||
protected val imageView: ShapeableImageView = itemView.findViewById(R.id.media_gallery_image)
|
||||
protected val playOverlay: ImageView = itemView.findViewById(R.id.media_gallery_play_overlay)
|
||||
protected val checkView: ImageView? = itemView.findViewById(R.id.media_gallery_check)
|
||||
protected val checkView: TextView? = itemView.findViewById(R.id.media_gallery_check)
|
||||
protected val title: TextView? = itemView.findViewById(R.id.media_gallery_title)
|
||||
|
||||
init {
|
||||
(itemView as AspectRatioFrameLayout).setAspectRatio(1f)
|
||||
}
|
||||
}
|
||||
|
||||
class FolderViewHolder(itemView: View, private val onMediaFolderClicked: OnMediaFolderClicked) : BaseViewHolder<FolderModel>(itemView) {
|
||||
@@ -74,27 +75,81 @@ object MediaGallerySelectableItem {
|
||||
}
|
||||
}
|
||||
|
||||
data class FileModel(val media: Media, val isSelected: Boolean) : MappingModel<FileModel> {
|
||||
data class FileModel(val media: Media, val isSelected: Boolean, val selectionOneBasedIndex: Int) : MappingModel<FileModel> {
|
||||
override fun areItemsTheSame(newItem: FileModel): Boolean {
|
||||
return newItem.media == media
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(newItem: FileModel): Boolean {
|
||||
return newItem.media == media && isSelected == newItem.isSelected
|
||||
return newItem.media == media && isSelected == newItem.isSelected && selectionOneBasedIndex == newItem.selectionOneBasedIndex
|
||||
}
|
||||
|
||||
override fun getChangePayload(newItem: FileModel): Any? {
|
||||
return when {
|
||||
newItem.media != media -> null
|
||||
newItem.isSelected != isSelected -> PAYLOAD_CHECK_CHANGED
|
||||
newItem.selectionOneBasedIndex != selectionOneBasedIndex -> PAYLOAD_INDEX_CHANGED
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FileViewHolder(itemView: View, private val onMediaClicked: OnMediaClicked) : BaseViewHolder<FileModel>(itemView) {
|
||||
|
||||
private val selectedPadding = DimensionUnit.DP.toPixels(12f)
|
||||
private val selectedRadius = DimensionUnit.DP.toPixels(12f)
|
||||
private var animator: ValueAnimator? = null
|
||||
|
||||
override fun bind(model: FileModel) {
|
||||
checkView?.visible = model.isSelected
|
||||
checkView?.text = "${model.selectionOneBasedIndex}"
|
||||
itemView.setOnClickListener { onMediaClicked(model.media, model.isSelected) }
|
||||
playOverlay.visible = MediaUtil.isVideo(model.media.mimeType) && !model.media.isVideoGif
|
||||
title?.visible = false
|
||||
|
||||
if (PAYLOAD_INDEX_CHANGED in payload) {
|
||||
return
|
||||
}
|
||||
|
||||
if (PAYLOAD_CHECK_CHANGED in payload) {
|
||||
animateCheckState(model.isSelected)
|
||||
return
|
||||
} else {
|
||||
animator?.cancel()
|
||||
updateImageView(if (model.isSelected) 1f else 0f)
|
||||
}
|
||||
|
||||
GlideApp.with(imageView)
|
||||
.load(DecryptableStreamUriLoader.DecryptableUri(model.media.uri))
|
||||
.addListener(ErrorLoggingRequestListener(FILE_VIEW_HOLDER_TAG))
|
||||
.into(imageView)
|
||||
}
|
||||
|
||||
checkView?.isSelected = model.isSelected
|
||||
playOverlay.visible = MediaUtil.isVideo(model.media.mimeType) && !model.media.isVideoGif
|
||||
itemView.setOnClickListener { onMediaClicked(model.media, model.isSelected) }
|
||||
title?.visible = false
|
||||
private fun animateCheckState(isSelected: Boolean) {
|
||||
animator?.cancel()
|
||||
|
||||
val start = if (isSelected) 0f else 1f
|
||||
val end = if (isSelected) 1f else 0f
|
||||
|
||||
animator = ValueAnimator.ofFloat(start, end).apply {
|
||||
addUpdateListener { animator ->
|
||||
val fraction = animator.animatedValue as Float
|
||||
updateImageView(fraction)
|
||||
}
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
animator?.cancel()
|
||||
}
|
||||
|
||||
private fun updateImageView(fraction: Float) {
|
||||
val padding = selectedPadding * fraction
|
||||
imageView.setPadding(padding.toInt())
|
||||
|
||||
val corners = selectedRadius * fraction
|
||||
imageView.shapeAppearanceModel = imageView.shapeAppearanceModel.withCornerSize(corners)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ class MediaGalleryViewModel(bucketId: String?, bucketTitle: String?, private val
|
||||
state.copy(
|
||||
bucketId = bucketId, bucketTitle = bucketTitle,
|
||||
items = media.map {
|
||||
MediaGallerySelectableItem.FileModel(it, false)
|
||||
MediaGallerySelectableItem.FileModel(it, false, 0)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -283,8 +283,8 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment) {
|
||||
private fun presentImageQualityToggle(quality: SentMediaQuality) {
|
||||
qualityButton.setImageResource(
|
||||
when (quality) {
|
||||
SentMediaQuality.STANDARD -> R.drawable.ic_sq_36
|
||||
SentMediaQuality.HIGH -> R.drawable.ic_hq_36
|
||||
SentMediaQuality.STANDARD -> R.drawable.ic_sq_24
|
||||
SentMediaQuality.HIGH -> R.drawable.ic_hq_24
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ object MediaReviewSelectedItem {
|
||||
|
||||
private val imageView: ImageView = itemView.findViewById(R.id.media_review_selected_image)
|
||||
private val playOverlay: ImageView = itemView.findViewById(R.id.media_review_play_overlay)
|
||||
private val selectedOverlay: ImageView = itemView.findViewById(R.id.media_review_selected_overlay)
|
||||
|
||||
override fun bind(model: Model) {
|
||||
Glide.with(imageView)
|
||||
@@ -43,7 +42,7 @@ object MediaReviewSelectedItem {
|
||||
.into(imageView)
|
||||
|
||||
playOverlay.visible = MediaUtil.isNonGifVideo(model.media) && !model.isSelected
|
||||
selectedOverlay.isSelected = model.isSelected
|
||||
imageView.isSelected = model.isSelected
|
||||
|
||||
itemView.contentDescription = if (model.isSelected) {
|
||||
context.getString(R.string.MediaReviewSelectedItem__tap_to_remove)
|
||||
|
||||
@@ -17,10 +17,12 @@ import android.view.ViewGroup;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
@@ -249,16 +251,17 @@ public final class ImageEditorFragment extends Fragment implements ImageEditorHu
|
||||
restoredModel = null;
|
||||
}
|
||||
|
||||
@ColorInt int blackoutColor = ContextCompat.getColor(requireContext(), R.color.signal_colorBackground);
|
||||
if (editorModel == null) {
|
||||
switch (mode) {
|
||||
case AVATAR_EDIT:
|
||||
editorModel = EditorModel.createForAvatarEdit();
|
||||
editorModel = EditorModel.createForAvatarEdit(blackoutColor);
|
||||
break;
|
||||
case AVATAR_CAPTURE:
|
||||
editorModel = EditorModel.createForAvatarCapture();
|
||||
editorModel = EditorModel.createForAvatarCapture(blackoutColor);
|
||||
break;
|
||||
default:
|
||||
editorModel = EditorModel.create();
|
||||
editorModel = EditorModel.create(blackoutColor);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -169,4 +169,8 @@ public final class FullscreenHelper {
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||
}
|
||||
|
||||
public static void setLowProfileMode(@NonNull Window window) {
|
||||
window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public final class WallpaperCropActivity extends BaseActivity {
|
||||
int width = displayMetrics.widthPixels;
|
||||
float ratio = width / (float) height;
|
||||
|
||||
EditorModel editorModel = EditorModel.createForWallpaperEditing(ratio);
|
||||
EditorModel editorModel = EditorModel.createForWallpaperEditing(ratio, ContextCompat.getColor(this, R.color.signal_colorBackground));
|
||||
|
||||
EditorElement image = new EditorElement(new UriGlideRenderer(imageUri, true, width, height, UriGlideRenderer.WEAK_BLUR));
|
||||
image.getFlags()
|
||||
|
||||
Reference in New Issue
Block a user