Add thumbnail shared element animation.

main
Alex Hart 2023-02-09 14:38:48 -04:00 zatwierdzone przez Greyson Parrelli
rodzic 2c48d40375
commit d0de43a6b2
25 zmienionych plików z 484 dodań i 252 usunięć

Wyświetl plik

@ -10,10 +10,11 @@ import androidx.lifecycle.Observer;
import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState; import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.contactshare.Contact; import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.conversation.ConversationItem;
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode;
import org.thoughtcrime.securesms.conversation.ConversationMessage; import org.thoughtcrime.securesms.conversation.ConversationMessage;
import org.thoughtcrime.securesms.conversation.colors.Colorizable; import org.thoughtcrime.securesms.conversation.colors.Colorizable;
import org.thoughtcrime.securesms.conversation.colors.Colorizer; import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.ConversationItemDisplayMode;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart; import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.conversation.mutiselect.Multiselectable; import org.thoughtcrime.securesms.conversation.mutiselect.Multiselectable;
import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord; import org.thoughtcrime.securesms.database.model.InMemoryMessageRecord;
@ -23,6 +24,7 @@ import org.thoughtcrime.securesms.giph.mp4.GiphyMp4Playable;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange; import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@ -113,5 +115,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord); void onViewGiftBadgeClicked(@NonNull MessageRecord messageRecord);
void onGiftBadgeRevealed(@NonNull MessageRecord messageRecord); void onGiftBadgeRevealed(@NonNull MessageRecord messageRecord);
void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args);
} }
} }

Wyświetl plik

@ -97,7 +97,7 @@ public class ThreadPhotoRailView extends FrameLayout {
} }
imageView.setOnClickListener(v -> { imageView.setOnClickListener(v -> {
if (clickedListener != null) clickedListener.onItemClicked(mediaRecord); if (clickedListener != null) clickedListener.onItemClicked(imageView, mediaRecord);
}); });
} }
@ -118,6 +118,6 @@ public class ThreadPhotoRailView extends FrameLayout {
} }
public interface OnItemClickedListener { public interface OnItemClickedListener {
void onItemClicked(MediaTable.MediaRecord mediaRecord); void onItemClicked(View itemView, MediaTable.MediaRecord mediaRecord);
} }
} }

Wyświetl plik

@ -8,6 +8,7 @@ import android.view.View
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.app.ActivityOptionsCompat import androidx.core.app.ActivityOptionsCompat
import androidx.core.util.Pair import androidx.core.util.Pair
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity import org.thoughtcrime.securesms.components.settings.DSLSettingsActivity
import org.thoughtcrime.securesms.groups.GroupId import org.thoughtcrime.securesms.groups.GroupId
@ -22,6 +23,7 @@ class ConversationSettingsActivity : DSLSettingsActivity(), ConversationSettings
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
ActivityCompat.postponeEnterTransition(this) ActivityCompat.postponeEnterTransition(this)
setExitSharedElementCallback(MaterialContainerTransformSharedElementCallback())
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
} }

Wyświetl plik

@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.components.settings.conversation package org.thoughtcrime.securesms.components.settings.conversation
import android.app.ActivityOptions
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -528,10 +529,13 @@ class ConversationSettingsFragment : DSLSettingsFragment(
SharedMediaPreference.Model( SharedMediaPreference.Model(
mediaCursor = state.sharedMedia, mediaCursor = state.sharedMedia,
mediaIds = state.sharedMediaIds, mediaIds = state.sharedMediaIds,
onMediaRecordClick = { mediaRecord, isLtr -> onMediaRecordClick = { view, mediaRecord, isLtr ->
view.transitionName = "thumb"
val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), view, "thumb")
startActivityForResult( startActivityForResult(
MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr, allMediaInRail = true), MediaIntentFactory.intentFromMediaRecord(requireContext(), mediaRecord, isLtr, allMediaInRail = true),
REQUEST_CODE_RETURN_FROM_MEDIA REQUEST_CODE_RETURN_FROM_MEDIA,
options.toBundle()
) )
} }
) )

Wyświetl plik

@ -24,7 +24,7 @@ object SharedMediaPreference {
class Model( class Model(
val mediaCursor: Cursor, val mediaCursor: Cursor,
val mediaIds: List<Long>, val mediaIds: List<Long>,
val onMediaRecordClick: (MediaTable.MediaRecord, Boolean) -> Unit val onMediaRecordClick: (View, MediaTable.MediaRecord, Boolean) -> Unit
) : PreferenceModel<Model>() { ) : PreferenceModel<Model>() {
override fun areItemsTheSame(newItem: Model): Boolean { override fun areItemsTheSame(newItem: Model): Boolean {
return true return true
@ -42,8 +42,8 @@ object SharedMediaPreference {
override fun bind(model: Model) { override fun bind(model: Model) {
rail.setCursor(GlideApp.with(rail), model.mediaCursor) rail.setCursor(GlideApp.with(rail), model.mediaCursor)
rail.setListener { rail.setListener { v, m ->
model.onMediaRecordClick(it, ViewUtil.isLtr(rail)) model.onMediaRecordClick(v, m, ViewUtil.isLtr(rail))
} }
} }
} }

Wyświetl plik

@ -4,6 +4,7 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.Window
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.PublishSubject
@ -34,6 +35,8 @@ open class ConversationActivity : PassphraseRequiredActivity(), ConversationPare
} }
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
window.requestFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
if (savedInstanceState != null) { if (savedInstanceState != null) {
shareDataTimestamp = savedInstanceState.getLong(STATE_WATERMARK, -1L) shareDataTimestamp = savedInstanceState.getLong(STATE_WATERMARK, -1L)
} else if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) { } else if (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) {

Wyświetl plik

@ -21,6 +21,7 @@ import android.animation.Animator;
import android.animation.LayoutTransition; import android.animation.LayoutTransition;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -38,6 +39,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.Window;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.FrameLayout; import android.widget.FrameLayout;
@ -68,6 +70,7 @@ import com.annimon.stream.Collectors;
import com.annimon.stream.Stream; import com.annimon.stream.Stream;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.signal.core.util.DimensionUnit; import org.signal.core.util.DimensionUnit;
@ -138,6 +141,8 @@ import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.longmessage.LongMessageFragment; import org.thoughtcrime.securesms.longmessage.LongMessageFragment;
import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder; import org.thoughtcrime.securesms.main.Material3OnScrollHelperBinder;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity;
import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment; import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment;
import org.thoughtcrime.securesms.messagerequests.MessageRequestState; import org.thoughtcrime.securesms.messagerequests.MessageRequestState;
import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel; import org.thoughtcrime.securesms.messagerequests.MessageRequestViewModel;
@ -2100,6 +2105,24 @@ public class ConversationFragment extends LoggingFragment implements Multiselect
} }
} }
@Override
public void goToMediaPreview(ConversationItem parent, View sharedElement, MediaIntentFactory.MediaPreviewArgs args) {
if (args.isVideoGif()) {
int adapterPosition = list.getChildAdapterPosition(parent);
GiphyMp4ProjectionPlayerHolder holder = giphyMp4ProjectionRecycler.getCurrentHolder(adapterPosition);
if (holder != null) {
parent.showProjectionArea();
holder.hide();
}
}
sharedElement.setTransitionName(MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME);
requireActivity().setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), sharedElement, MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME);
requireActivity().startActivity(MediaIntentFactory.create(requireActivity(), args), options.toBundle());
}
@Override @Override
public void onActivatePaymentsClicked() { public void onActivatePaymentsClicked() {
Intent intent = new Intent(requireContext(), PaymentsActivity.class); Intent intent = new Intent(requireContext(), PaymentsActivity.class);

Wyświetl plik

@ -163,7 +163,6 @@ import kotlin.jvm.functions.Function1;
* thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter. * thread. Used by ComposeMessageActivity's ListActivity via a ConversationAdapter.
* *
* @author Moxie Marlinspike * @author Moxie Marlinspike
*
*/ */
public final class ConversationItem extends RelativeLayout implements BindableConversationItem, public final class ConversationItem extends RelativeLayout implements BindableConversationItem,
@ -1659,8 +1658,7 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE); if (sharedContactStub.resolved()) sharedContactStub.get().getFooter().setVisibility(GONE);
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().getFooter().setVisibility(GONE); if (mediaThumbnailStub.resolved()) mediaThumbnailStub.require().getFooter().setVisibility(GONE);
if (isFooterVisible(current, next, isGroupThread)) if (isFooterVisible(current, next, isGroupThread)) {
{
ConversationItemFooter activeFooter = getActiveFooter(current); ConversationItemFooter activeFooter = getActiveFooter(current);
activeFooter.setVisibility(VISIBLE); activeFooter.setVisibility(VISIBLE);
activeFooter.setMessageRecord(current, locale); activeFooter.setMessageRecord(current, locale);
@ -2379,8 +2377,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
false, false,
false, false,
MediaTable.Sorting.Newest, MediaTable.Sorting.Newest,
slide.isVideoGif()); slide.isVideoGif(),
context.startActivity(MediaIntentFactory.create(context, args)); new MediaIntentFactory.SharedElementArgs(
slide.asAttachment().getWidth(),
slide.asAttachment().getHeight(),
mediaThumbnailStub.require().getCorners().getTopLeft(),
mediaThumbnailStub.require().getCorners().getTopRight(),
mediaThumbnailStub.require().getCorners().getBottomRight(),
mediaThumbnailStub.require().getCorners().getBottomLeft()
));
eventListener.goToMediaPreview(ConversationItem.this, mediaThumbnailStub.require(), args);
} else if (slide.getUri() != null) { } else if (slide.getUri() != null) {
Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType()); Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri()); Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri());

Wyświetl plik

@ -114,7 +114,10 @@ public final class GiphyMp4ProjectionRecycler implements GiphyMp4PlaybackControl
}); });
holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer()); holder.playContent(giphyMp4Playable.getMediaItem(), giphyMp4Playable.getPlaybackPolicyEnforcer());
} else { } else {
giphyMp4Playable.showProjectionArea();
holder.setOnPlaybackReady(() -> { holder.setOnPlaybackReady(() -> {
holder.show();
giphyMp4Playable.hideProjectionArea(); giphyMp4Playable.hideProjectionArea();
parent.invalidate(); parent.invalidate();
}); });

Wyświetl plik

@ -345,7 +345,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
} }
thumbnailView.setImageResource(glideRequests, slide, false, false); thumbnailView.setImageResource(glideRequests, slide, false, false);
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(thumbnailView, mediaRecord));
thumbnailView.setOnLongClickListener(view -> onLongClick()); thumbnailView.setOnLongClickListener(view -> onLongClick());
} }
@ -411,7 +411,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
line1.setText(fileName.orElse(fileTypeDescription)); line1.setText(fileName.orElse(fileTypeDescription));
line2.setText(getLine2(context, mediaRecord, slide)); line2.setText(getLine2(context, mediaRecord, slide));
itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(getTransitionAnchor(), mediaRecord));
itemView.setOnLongClickListener(view -> onLongClick()); itemView.setOnLongClickListener(view -> onLongClick());
selectForMarque = () -> line1.setSelected(true); selectForMarque = () -> line1.setSelected(true);
handler = new Handler(Looper.getMainLooper()); handler = new Handler(Looper.getMainLooper());
@ -459,6 +459,10 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
return fileName.orElse(null); return fileName.orElse(null);
} }
protected @NonNull View getTransitionAnchor() {
return itemView;
}
private @NonNull String describe(@NonNull Recipient from, @NonNull Recipient thread) { private @NonNull String describe(@NonNull Recipient from, @NonNull Recipient thread) {
if (from == Recipient.UNKNOWN && thread == Recipient.UNKNOWN) { if (from == Recipient.UNKNOWN && thread == Recipient.UNKNOWN) {
return fileName.orElse(fileTypeDescription); return fileName.orElse(fileTypeDescription);
@ -541,8 +545,8 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
audioView.setAudio((AudioSlide) slide, new AudioViewCallbacksAdapter(audioItemListener, mmsId), true, true); audioView.setAudio((AudioSlide) slide, new AudioViewCallbacksAdapter(audioItemListener, mmsId), true, true);
audioItemListener.registerPlaybackStateObserver(audioView.getPlaybackStateObserver()); audioItemListener.registerPlaybackStateObserver(audioView.getPlaybackStateObserver());
audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); audioView.setOnClickListener(view -> itemClickListener.onMediaClicked(audioView, mediaRecord));
itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); itemView.setOnClickListener(view -> itemClickListener.onMediaClicked(audioView, mediaRecord));
} }
@Override @Override
@ -584,10 +588,15 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
super.bind(context, mediaRecord, slide); super.bind(context, mediaRecord, slide);
this.slide = slide; this.slide = slide;
thumbnailView.setImageResource(glideRequests, slide, false, false); thumbnailView.setImageResource(glideRequests, slide, false, false);
thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(thumbnailView, mediaRecord));
thumbnailView.setOnLongClickListener(view -> onLongClick()); thumbnailView.setOnLongClickListener(view -> onLongClick());
} }
@Override
protected @NonNull View getTransitionAnchor() {
return thumbnailView;
}
@Override @Override
protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) { protected String getFileTypeDescription(@NonNull Context context, @NonNull Slide slide) {
if (slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video); if (slide.hasVideo()) return context.getString(R.string.MediaOverviewActivity_video);
@ -648,7 +657,7 @@ final class MediaGalleryAllAdapter extends StickyHeaderGridAdapter {
} }
interface ItemClickListener { interface ItemClickListener {
void onMediaClicked(@NonNull MediaTable.MediaRecord mediaRecord); void onMediaClicked(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord);
void onMediaLongClicked(MediaTable.MediaRecord mediaRecord); void onMediaLongClicked(MediaTable.MediaRecord mediaRecord);
} }

Wyświetl plik

@ -34,6 +34,7 @@ import androidx.viewpager.widget.ViewPager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayout;
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback;
import org.signal.libsignal.protocol.util.Pair; import org.signal.libsignal.protocol.util.Pair;
import org.thoughtcrime.securesms.PassphraseRequiredActivity; import org.thoughtcrime.securesms.PassphraseRequiredActivity;
@ -91,6 +92,7 @@ public final class MediaOverviewActivity extends PassphraseRequiredActivity {
@Override @Override
protected void onCreate(Bundle bundle, boolean ready) { protected void onCreate(Bundle bundle, boolean ready) {
setExitSharedElementCallback(new MaterialContainerTransformSharedElementCallback());
setContentView(R.layout.media_overview_activity); setContentView(R.layout.media_overview_activity);
initializeResources(); initializeResources();

Wyświetl plik

@ -1,11 +1,13 @@
package org.thoughtcrime.securesms.mediaoverview; package org.thoughtcrime.securesms.mediaoverview;
import android.app.ActivityOptions;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.util.Size;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@ -27,6 +29,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager; import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
import org.signal.core.util.DimensionUnit;
import org.signal.core.util.logging.Log; import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment; import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
@ -38,6 +41,7 @@ import org.thoughtcrime.securesms.database.MediaTable;
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader; import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader;
import org.thoughtcrime.securesms.database.loaders.MediaLoader; import org.thoughtcrime.securesms.database.loaders.MediaLoader;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory; import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.PartAuthority; import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.BottomOffsetDecoration; import org.thoughtcrime.securesms.util.BottomOffsetDecoration;
@ -196,11 +200,11 @@ public final class MediaOverviewPageFragment extends Fragment
} }
@Override @Override
public void onMediaClicked(@NonNull MediaTable.MediaRecord mediaRecord) { public void onMediaClicked(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord) {
if (actionMode != null) { if (actionMode != null) {
handleMediaMultiSelectClick(mediaRecord); handleMediaMultiSelectClick(mediaRecord);
} else { } else {
handleMediaPreviewClick(mediaRecord); handleMediaPreviewClick(view, mediaRecord);
} }
} }
@ -224,7 +228,7 @@ public final class MediaOverviewPageFragment extends Fragment
} }
} }
private void handleMediaPreviewClick(@NonNull MediaTable.MediaRecord mediaRecord) { private void handleMediaPreviewClick(@NonNull View view, @NonNull MediaTable.MediaRecord mediaRecord) {
if (mediaRecord.getAttachment().getUri() == null) { if (mediaRecord.getAttachment().getUri() == null) {
return; return;
} }
@ -249,8 +253,18 @@ public final class MediaOverviewPageFragment extends Fragment
threadId == MediaTable.ALL_THREADS, threadId == MediaTable.ALL_THREADS,
true, true,
sorting, sorting,
attachment.isVideoGif()); attachment.isVideoGif(),
context.startActivity(MediaIntentFactory.create(context, args)); new MediaIntentFactory.SharedElementArgs(
attachment.getWidth(),
attachment.getHeight(),
DimensionUnit.DP.toDp(12),
DimensionUnit.DP.toDp(12),
DimensionUnit.DP.toDp(12),
DimensionUnit.DP.toDp(12)
));
view.setTransitionName(MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME);
ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(requireActivity(), view, MediaPreviewV2Activity.SHARED_ELEMENT_TRANSITION_NAME);
context.startActivity(MediaIntentFactory.create(context, args), options.toBundle());
} else { } else {
if (!MediaUtil.isAudio(attachment)) { if (!MediaUtil.isAudio(attachment)) {
showFileExternally(context, mediaRecord); showFileExternally(context, mediaRecord);

Wyświetl plik

@ -7,16 +7,22 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.ZoomingImageView; import org.thoughtcrime.securesms.components.ZoomingImageView;
import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
public final class ImageMediaPreviewFragment extends MediaPreviewFragment { public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
private MediaPreviewPlayerControlView bottomBarControlView; private MediaPreviewPlayerControlView bottomBarControlView;
private MediaPreviewV2ViewModel viewModel;
private LifecycleDisposable lifecycleDisposable;
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -24,12 +30,19 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState)
ZoomingImageView zoomingImageView = (ZoomingImageView) inflater.inflate(R.layout.media_preview_image_fragment, container, false); {
View view = inflater.inflate(R.layout.media_preview_image_fragment, container, false);
GlideRequests glideRequests = GlideApp.with(requireActivity()); GlideRequests glideRequests = GlideApp.with(requireActivity());
Bundle arguments = requireArguments(); Bundle arguments = requireArguments();
Uri uri = arguments.getParcelable(DATA_URI); Uri uri = arguments.getParcelable(DATA_URI);
String contentType = arguments.getString(DATA_CONTENT_TYPE); String contentType = arguments.getString(DATA_CONTENT_TYPE);
ZoomingImageView zoomingImageView = view.findViewById(R.id.zooming_image_view);
viewModel = new ViewModelProvider(requireActivity()).get(MediaPreviewV2ViewModel.class);
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.bindTo(getViewLifecycleOwner());
if (!MediaUtil.isImageType(contentType)) { if (!MediaUtil.isImageType(contentType)) {
throw new AssertionError("This fragment can only display images"); throw new AssertionError("This fragment can only display images");
@ -40,7 +53,11 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment {
zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia()); zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia());
return zoomingImageView; lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> {
zoomingImageView.setVisibility(state.isInSharedAnimation() ? View.INVISIBLE : View.VISIBLE);
}));
return view;
} }
@Override @Override

Wyświetl plik

@ -6,6 +6,7 @@ import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.signal.core.util.dp
import org.thoughtcrime.securesms.attachments.DatabaseAttachment import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.MediaTable import org.thoughtcrime.securesms.database.MediaTable
import org.thoughtcrime.securesms.database.MediaTable.MediaRecord import org.thoughtcrime.securesms.database.MediaTable.MediaRecord
@ -15,15 +16,16 @@ object MediaIntentFactory {
const val NOT_IN_A_THREAD = -2 const val NOT_IN_A_THREAD = -2
const val UNKNOWN_TIMESTAMP = -2 const val UNKNOWN_TIMESTAMP = -2
const val THREAD_ID_EXTRA = "thread_id"
const val DATE_EXTRA = "date" @Parcelize
const val SIZE_EXTRA = "size" data class SharedElementArgs(
const val CAPTION_EXTRA = "caption" val width: Int = 1,
const val LEFT_IS_RECENT_EXTRA = "left_is_recent" val height: Int = 1,
const val HIDE_ALL_MEDIA_EXTRA = "came_from_all_media" val topLeft: Float = 0f,
const val SHOW_THREAD_EXTRA = "show_thread" val topRight: Float = 0f,
const val SORTING_EXTRA = "sorting" val bottomRight: Float = 0f,
const val IS_VIDEO_GIF = "is_video_gif" val bottomLeft: Float = 0f
) : Parcelable
@Parcelize @Parcelize
data class MediaPreviewArgs( data class MediaPreviewArgs(
@ -38,7 +40,8 @@ object MediaIntentFactory {
val showThread: Boolean = false, val showThread: Boolean = false,
val allMediaInRail: Boolean = false, val allMediaInRail: Boolean = false,
val sorting: MediaTable.Sorting, val sorting: MediaTable.Sorting,
val isVideoGif: Boolean val isVideoGif: Boolean,
val sharedElementArgs: SharedElementArgs = SharedElementArgs()
) : Parcelable ) : Parcelable
@JvmStatic @JvmStatic
@ -68,7 +71,15 @@ object MediaIntentFactory {
leftIsRecent, leftIsRecent,
allMediaInRail = allMediaInRail, allMediaInRail = allMediaInRail,
sorting = MediaTable.Sorting.Newest, sorting = MediaTable.Sorting.Newest,
isVideoGif = attachment.isVideoGif isVideoGif = attachment.isVideoGif,
sharedElementArgs = SharedElementArgs(
attachment.width,
attachment.height,
12.dp.toFloat(),
12.dp.toFloat(),
12.dp.toFloat(),
12.dp.toFloat()
)
) )
) )
} }

Wyświetl plik

@ -1,28 +1,104 @@
package org.thoughtcrime.securesms.mediapreview package org.thoughtcrime.securesms.mediapreview
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.view.View
import android.widget.ImageView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.transition.addListener
import androidx.core.view.animation.PathInterpolatorCompat
import androidx.fragment.app.commit import androidx.fragment.app.commit
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy
import com.google.android.material.shape.ShapeAppearanceModel
import com.google.android.material.transition.platform.MaterialContainerTransform
import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback
import org.thoughtcrime.securesms.PassphraseRequiredActivity import org.thoughtcrime.securesms.PassphraseRequiredActivity
import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaController
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner
import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.util.ActionRequestListener
import org.thoughtcrime.securesms.util.LifecycleDisposable
class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner { class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaControllerOwner {
override lateinit var voiceNoteMediaController: VoiceNoteMediaController override lateinit var voiceNoteMediaController: VoiceNoteMediaController
private val viewModel: MediaPreviewV2ViewModel by viewModels()
private val lifecycleDisposable = LifecycleDisposable()
private lateinit var transitionImageView: ImageView
override fun attachBaseContext(newBase: Context) { override fun attachBaseContext(newBase: Context) {
delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES delegate.localNightMode = AppCompatDelegate.MODE_NIGHT_YES
super.attachBaseContext(newBase) super.attachBaseContext(newBase)
} }
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
val args = MediaIntentFactory.requireArguments(intent.extras!!)
val originalCorners = ShapeAppearanceModel.Builder()
.setTopLeftCornerSize(args.sharedElementArgs.topLeft)
.setTopRightCornerSize(args.sharedElementArgs.topRight)
.setBottomRightCornerSize(args.sharedElementArgs.bottomRight)
.setBottomLeftCornerSize(args.sharedElementArgs.bottomLeft)
.build()
postponeEnterTransition()
setEnterSharedElementCallback(MaterialContainerTransformSharedElementCallback())
window.sharedElementEnterTransition = MaterialContainerTransform().apply {
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
startShapeAppearanceModel = originalCorners
endShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
duration = 250L
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
addListener(
onStart = {
transitionImageView.visibility = View.VISIBLE
viewModel.setIsInSharedAnimation(true)
},
onEnd = {
transitionImageView.clearAnimation()
transitionImageView.visibility = View.INVISIBLE
viewModel.setIsInSharedAnimation(false)
}
)
}
window.sharedElementExitTransition = MaterialContainerTransform().apply {
addTarget(SHARED_ELEMENT_TRANSITION_NAME)
startShapeAppearanceModel = ShapeAppearanceModel.builder().setAllCornerSizes(0f).build()
endShapeAppearanceModel = originalCorners
duration = 250L
interpolator = PathInterpolatorCompat.create(0.17f, 0.17f, 0f, 1f)
addListener(
onStart = {
transitionImageView.visibility = View.VISIBLE
viewModel.setIsInSharedAnimation(true)
},
onEnd = {
transitionImageView.clearAnimation()
transitionImageView.visibility = View.INVISIBLE
viewModel.setIsInSharedAnimation(false)
}
)
}
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
setTheme(R.style.TextSecure_MediaPreview) setTheme(R.style.TextSecure_MediaPreview)
setContentView(R.layout.activity_mediapreview_v2) setContentView(R.layout.activity_mediapreview_v2)
transitionImageView = findViewById(R.id.transition_image_view)
lifecycleDisposable += viewModel.state.subscribe { state ->
if (state.position in state.mediaRecords.indices) {
setTransitionImage(state.mediaRecords[state.position].attachment?.uri)
}
}
voiceNoteMediaController = VoiceNoteMediaController(this) voiceNoteMediaController = VoiceNoteMediaController(this)
val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface) val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface)
@ -31,7 +107,6 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
if (savedInstanceState == null) { if (savedInstanceState == null) {
val bundle = Bundle() val bundle = Bundle()
val args = MediaIntentFactory.requireArguments(intent.extras!!)
bundle.putParcelable(MediaPreviewV2Fragment.ARGS_KEY, args) bundle.putParcelable(MediaPreviewV2Fragment.ARGS_KEY, args)
supportFragmentManager.commit { supportFragmentManager.commit {
setReorderingAllowed(true) setReorderingAllowed(true)
@ -40,7 +115,23 @@ class MediaPreviewV2Activity : PassphraseRequiredActivity(), VoiceNoteMediaContr
} }
} }
private fun setTransitionImage(mediaUri: Uri?) {
if (mediaUri == null) {
GlideApp.with(this).clear(transitionImageView)
return
}
GlideApp.with(this)
.load(DecryptableStreamUriLoader.DecryptableUri(mediaUri))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontTransform()
.downsample(DownsampleStrategy.FIT_CENTER)
.addListener(ActionRequestListener.onEither { startPostponedEnterTransition() })
.into(transitionImageView)
}
companion object { companion object {
private const val FRAGMENT_TAG = "media_preview_fragment_v2" private const val FRAGMENT_TAG = "media_preview_fragment_v2"
const val SHARED_ELEMENT_TRANSITION_NAME = "thumb"
} }
} }

Wyświetl plik

@ -79,7 +79,9 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
private val lifecycleDisposable = LifecycleDisposable() private val lifecycleDisposable = LifecycleDisposable()
private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind) private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind)
private val viewModel: MediaPreviewV2ViewModel by viewModels() private val viewModel: MediaPreviewV2ViewModel by viewModels(ownerProducer = {
requireActivity()
})
private val debouncer = Debouncer(2, TimeUnit.SECONDS) private val debouncer = Debouncer(2, TimeUnit.SECONDS)
private lateinit var pagerAdapter: MediaPreviewV2Adapter private lateinit var pagerAdapter: MediaPreviewV2Adapter

Wyświetl plik

@ -12,7 +12,8 @@ data class MediaPreviewV2State(
val allMediaInAlbumRail: Boolean = false, val allMediaInAlbumRail: Boolean = false,
val leftIsRecent: Boolean = false, val leftIsRecent: Boolean = false,
val albums: Map<Long, List<Media>> = mapOf(), val albums: Map<Long, List<Media>> = mapOf(),
val messageBodies: Map<Long, SpannableString> = mapOf() val messageBodies: Map<Long, SpannableString> = mapOf(),
val isInSharedAnimation: Boolean = true
) { ) {
enum class LoadState { INIT, DATA_LOADED, MEDIA_READY } enum class LoadState { INIT, DATA_LOADED, MEDIA_READY }
} }

Wyświetl plik

@ -27,6 +27,10 @@ class MediaPreviewV2ViewModel : ViewModel() {
val currentPosition: Int val currentPosition: Int
get() = store.state.position get() = store.state.position
fun setIsInSharedAnimation(isInSharedAnimation: Boolean) {
store.update { it.copy(isInSharedAnimation = isInSharedAnimation) }
}
fun fetchAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) { fun fetchAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) {
if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) { if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) {
disposables += store.update(repository.getAttachments(context, startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> disposables += store.update(repository.getAttachments(context, startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State ->

Wyświetl plik

@ -9,6 +9,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.exoplayer2.ui.PlayerControlView; import com.google.android.exoplayer2.ui.PlayerControlView;
@ -16,6 +17,7 @@ import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner; import org.thoughtcrime.securesms.components.voice.VoiceNoteMediaControllerOwner;
import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.mms.VideoSlide;
import org.thoughtcrime.securesms.util.LifecycleDisposable;
import org.thoughtcrime.securesms.util.MediaUtil; import org.thoughtcrime.securesms.util.MediaUtil;
import org.thoughtcrime.securesms.video.VideoPlayer; import org.thoughtcrime.securesms.video.VideoPlayer;
@ -29,6 +31,9 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment {
private VideoPlayer videoView; private VideoPlayer videoView;
private boolean isVideoGif; private boolean isVideoGif;
private MediaPreviewV2ViewModel viewModel;
private LifecycleDisposable lifecycleDisposable;
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -46,6 +51,13 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment {
} }
videoView = itemView.findViewById(R.id.video_player); videoView = itemView.findViewById(R.id.video_player);
viewModel = new ViewModelProvider(requireActivity()).get(MediaPreviewV2ViewModel.class);
lifecycleDisposable = new LifecycleDisposable();
lifecycleDisposable.add(viewModel.getState().distinctUntilChanged().subscribe(state -> {
Log.d(TAG, "ANIM" + state.isInSharedAnimation());
itemView.setVisibility(state.isInSharedAnimation() ? View.INVISIBLE : View.VISIBLE);
}));
videoView.setWindow(requireActivity().getWindow()); videoView.setWindow(requireActivity().getWindow());
videoView.setVideoSource(new VideoSlide(getContext(), uri, size, false), autoPlay, TAG); videoView.setVideoSource(new VideoSlide(getContext(), uri, size, false), autoPlay, TAG);

Wyświetl plik

@ -527,7 +527,8 @@ public class AttachmentManager {
false, false,
false, false,
MediaTable.Sorting.Newest, MediaTable.Sorting.Newest,
slide.isVideoGif()); slide.isVideoGif(),
new MediaIntentFactory.SharedElementArgs());
context.startActivity(MediaIntentFactory.create(context, args)); context.startActivity(MediaIntentFactory.create(context, args));
} }
} }

Wyświetl plik

@ -1,7 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/transition_image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:importantForAccessibility="no"
android:transitionName="thumb" />
<androidx.fragment.app.FragmentContainerView <androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view" android:id="@+id/fragment_container_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/signal_dark_colorNeutral" /> android:background="@color/signal_dark_colorNeutral" />
</FrameLayout>

Wyświetl plik

@ -1,21 +1,20 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.ConversationItemThumbnail <org.thoughtcrime.securesms.components.ConversationItemThumbnail xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
tools:viewBindingIgnore="true"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/image_view" android:id="@+id/image_view"
android:layout_width="@dimen/media_bubble_default_dimens" android:layout_width="@dimen/media_bubble_default_dimens"
android:layout_height="@dimen/media_bubble_default_dimens" android:layout_height="@dimen/media_bubble_default_dimens"
android:scaleType="centerCrop"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:contentDescription="@string/conversation_item__mms_image_description" android:contentDescription="@string/conversation_item__mms_image_description"
android:visibility="gone"
android:elevation="8dp" android:elevation="8dp"
app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width_solo" android:scaleType="centerCrop"
android:visibility="gone"
app:conversationThumbnail_gifWidth="@dimen/media_bubble_gif_width"
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height"
app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width" app:conversationThumbnail_maxWidth="@dimen/media_bubble_max_width"
app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height" app:conversationThumbnail_minHeight="@dimen/media_bubble_min_height"
app:conversationThumbnail_maxHeight="@dimen/media_bubble_max_height" app:conversationThumbnail_minWidth="@dimen/media_bubble_min_width_solo"
app:conversationThumbnail_gifWidth="@dimen/media_bubble_gif_width"
tools:src="@drawable/ic_video_light" tools:src="@drawable/ic_video_light"
tools:viewBindingIgnore="true"
tools:visibility="gone" /> tools:visibility="gone" />

Wyświetl plik

@ -1,8 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<org.thoughtcrime.securesms.components.ZoomingImageView xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<org.thoughtcrime.securesms.components.ZoomingImageView
android:id="@+id/zooming_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:clickable="false" android:clickable="false"
android:contentDescription="@string/media_preview_activity__media_content_description" /> android:contentDescription="@string/media_preview_activity__media_content_description" />
</FrameLayout>

Wyświetl plik

@ -1,14 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
tools:viewBindingIgnore="true">
<org.thoughtcrime.securesms.video.VideoPlayer <org.thoughtcrime.securesms.video.VideoPlayer
android:id="@+id/video_player" android:id="@+id/video_player"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"
android:layout_gravity="center" />
</FrameLayout> </FrameLayout>

Wyświetl plik

@ -43,6 +43,7 @@
<style name="TextSecure.MediaPreview" parent="@style/TextSecure.BaseMediaPreview"> <style name="TextSecure.MediaPreview" parent="@style/TextSecure.BaseMediaPreview">
<item name="android:navigationBarColor">@color/media_preview_bar_background</item> <item name="android:navigationBarColor">@color/media_preview_bar_background</item>
<item name="android:windowContentTransitions">true</item>
</style> </style>
<style name="Theme.Signal.Light.BottomSheetDialog" parent="Theme.Material3.Light.BottomSheetDialog"> <style name="Theme.Signal.Light.BottomSheetDialog" parent="Theme.Material3.Light.BottomSheetDialog">