Create new Media Preview infrastructure, behind feature flag.

fork-5.53.8
Nicholas 2022-09-30 09:42:06 -04:00 zatwierdzone przez Greyson Parrelli
rodzic 1af576c157
commit f63ce79f16
20 zmienionych plików z 406 dodań i 71 usunięć

Wyświetl plik

@ -686,6 +686,10 @@
android:screenOrientation="portrait"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".mediapreview.MediaPreviewV2Activity"
android:exported="false"
android:theme="@style/Theme.Signal.DayNight.NoActionBar" />
<service android:enabled="true" android:name=".exporter.SignalSmsExportService" android:foregroundServiceType="dataSync" />
<service android:enabled="true" android:name=".service.webrtc.WebRtcCallService" android:foregroundServiceType="camera|microphone"/>
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>

Wyświetl plik

@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord;
import org.thoughtcrime.securesms.database.loaders.PagingMediaLoader;
import org.thoughtcrime.securesms.mediaoverview.MediaOverviewActivity;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewFragment;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewViewModel;
import org.thoughtcrime.securesms.mediapreview.MediaRailAdapter;
@ -98,18 +99,6 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
private final static String TAG = Log.tag(MediaPreviewActivity.class);
private static final int NOT_IN_A_THREAD = -2;
public static final String THREAD_ID_EXTRA = "thread_id";
public static final String DATE_EXTRA = "date";
public static final String SIZE_EXTRA = "size";
public static final String CAPTION_EXTRA = "caption";
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
public static final String HIDE_ALL_MEDIA_EXTRA = "came_from_all_media";
public static final String SHOW_THREAD_EXTRA = "show_thread";
public static final String SORTING_EXTRA = "sorting";
public static final String IS_VIDEO_GIF = "is_video_gif";
private ViewPager mediaPager;
private View detailsContainer;
private TextView caption;
@ -127,7 +116,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
private ViewPagerListener viewPagerListener;
private int restartItem = -1;
private long threadId = NOT_IN_A_THREAD;
private long threadId = MediaIntentFactory.NOT_IN_A_THREAD;
private boolean cameFromAllMedia;
private boolean showThread;
private MediaDatabase.Sorting sorting;
@ -143,12 +132,12 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
{
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, mediaRecord.getThreadId());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, attachment.getSize());
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, attachment.getCaption());
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, leftIsRecent);
intent.putExtra(MediaPreviewActivity.IS_VIDEO_GIF, attachment.isVideoGif());
intent.putExtra(MediaIntentFactory.THREAD_ID_EXTRA, mediaRecord.getThreadId());
intent.putExtra(MediaIntentFactory.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaIntentFactory.SIZE_EXTRA, attachment.getSize());
intent.putExtra(MediaIntentFactory.CAPTION_EXTRA, attachment.getCaption());
intent.putExtra(MediaIntentFactory.LEFT_IS_RECENT_EXTRA, leftIsRecent);
intent.putExtra(MediaIntentFactory.IS_VIDEO_GIF, attachment.isVideoGif());
intent.setDataAndType(attachment.getUri(), mediaRecord.getContentType());
return intent;
}
@ -305,17 +294,17 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
private void initializeResources() {
Intent intent = getIntent();
threadId = intent.getLongExtra(THREAD_ID_EXTRA, NOT_IN_A_THREAD);
cameFromAllMedia = intent.getBooleanExtra(HIDE_ALL_MEDIA_EXTRA, false);
showThread = intent.getBooleanExtra(SHOW_THREAD_EXTRA, false);
sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(SORTING_EXTRA, 0)];
threadId = intent.getLongExtra(MediaIntentFactory.THREAD_ID_EXTRA, MediaIntentFactory.NOT_IN_A_THREAD);
cameFromAllMedia = intent.getBooleanExtra(MediaIntentFactory.HIDE_ALL_MEDIA_EXTRA, false);
showThread = intent.getBooleanExtra(MediaIntentFactory.SHOW_THREAD_EXTRA, false);
sorting = MediaDatabase.Sorting.values()[intent.getIntExtra(MediaIntentFactory.SORTING_EXTRA, 0)];
initialMediaUri = intent.getData();
initialMediaType = intent.getType();
initialMediaSize = intent.getLongExtra(SIZE_EXTRA, 0);
initialCaption = intent.getStringExtra(CAPTION_EXTRA);
leftIsRecent = intent.getBooleanExtra(LEFT_IS_RECENT_EXTRA, false);
initialMediaIsVideoGif = intent.getBooleanExtra(IS_VIDEO_GIF, false);
initialMediaSize = intent.getLongExtra(MediaIntentFactory.SIZE_EXTRA, 0);
initialCaption = intent.getStringExtra(MediaIntentFactory.CAPTION_EXTRA);
leftIsRecent = intent.getBooleanExtra(MediaIntentFactory.LEFT_IS_RECENT_EXTRA, false);
initialMediaIsVideoGif = intent.getBooleanExtra(MediaIntentFactory.IS_VIDEO_GIF, false);
restartItem = -1;
}
@ -533,7 +522,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
}
private boolean isMediaInDb() {
return threadId != NOT_IN_A_THREAD;
return threadId != MediaIntentFactory.NOT_IN_A_THREAD;
}
private @Nullable MediaItem getCurrentMediaItem() {
@ -789,7 +778,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
cursor.moveToPosition(cursorPosition);
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(context, cursor);
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(cursor);
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
MediaPreviewFragment fragment = MediaPreviewFragment.newInstance(attachment, autoPlay);
@ -819,7 +808,7 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
cursor.moveToPosition(cursorPosition);
MediaRecord mediaRecord = MediaRecord.from(context, cursor);
MediaRecord mediaRecord = MediaRecord.from(cursor);
DatabaseAttachment attachment = Objects.requireNonNull(mediaRecord.getAttachment());
RecipientId recipientId = mediaRecord.getRecipientId();
RecipientId threadRecipientId = mediaRecord.getThreadRecipientId();
@ -890,4 +879,4 @@ public final class MediaPreviewActivity extends PassphraseRequiredActivity
@Nullable View getPlaybackControls(int position);
boolean hasFragmentFor(int position);
}
}
}

Wyświetl plik

@ -4,11 +4,12 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.viewpager.widget.ViewPager;
import androidx.viewpager2.widget.ViewPager2;
/**
* Based on https://developer.android.com/training/animation/screen-slide#depth-page
*/
public final class DepthPageTransformer implements ViewPager.PageTransformer {
public final class DepthPageTransformer implements ViewPager.PageTransformer, ViewPager2.PageTransformer {
private static final float MIN_SCALE = 0.75f;
public void transformPage(@NonNull View view, float position) {

Wyświetl plik

@ -89,7 +89,7 @@ public class ThreadPhotoRailView extends FrameLayout {
@Override
public void onBindItemViewHolder(ThreadPhotoViewHolder viewHolder, @NonNull Cursor cursor) {
ThumbnailView imageView = viewHolder.imageView;
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(getContext(), cursor);
MediaDatabase.MediaRecord mediaRecord = MediaDatabase.MediaRecord.from(cursor);
Slide slide = MediaUtil.getSlideForAttachment(getContext(), mediaRecord.getAttachment());
if (slide != null) {

Wyświetl plik

@ -18,6 +18,7 @@ package org.thoughtcrime.securesms.conversation;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@ -96,6 +97,7 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectCollection;
import org.thoughtcrime.securesms.conversation.mutiselect.MultiselectPart;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.MessageDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
@ -111,6 +113,8 @@ import org.thoughtcrime.securesms.jobs.MmsSendJob;
import org.thoughtcrime.securesms.jobs.SmsSendJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.linkpreview.LinkPreview;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mediapreview.MediaPreviewV2Activity;
import org.thoughtcrime.securesms.mms.GlideRequests;
import org.thoughtcrime.securesms.mms.ImageSlide;
import org.thoughtcrime.securesms.mms.PartAuthority;
@ -126,6 +130,7 @@ import org.thoughtcrime.securesms.recipients.RecipientForeverObserver;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.revealable.ViewOnceMessageView;
import org.thoughtcrime.securesms.util.DateUtils;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.InterceptableLongClickCopyLinkSpan;
import org.thoughtcrime.securesms.util.LinkUtil;
import org.thoughtcrime.securesms.util.LongClickMovementMethod;
@ -2312,17 +2317,19 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
} else if (!canPlayContent && mediaItem != null && eventListener != null) {
eventListener.onPlayInlineContent(conversationMessage);
} else if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(slide.getUri(), slide.getContentType());
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, messageRecord.getThreadId());
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, messageRecord.getTimestamp());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize());
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, slide.getCaption().orElse(null));
intent.putExtra(MediaPreviewActivity.IS_VIDEO_GIF, slide.isVideoGif());
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, false);
context.startActivity(intent);
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
messageRecord.getThreadId(),
messageRecord.getTimestamp(),
slide.getUri(),
slide.getContentType(),
slide.asAttachment().getSize(),
slide.getCaption().orElse(null),
false,
false,
false,
MediaDatabase.Sorting.Newest.ordinal(),
slide.isVideoGif());
context.startActivity(MediaIntentFactory.create(context, args));
} else if (slide.getUri() != null) {
Log.i(TAG, "Clicked: " + slide.getUri() + " , " + slide.getContentType());
Uri publicUri = PartAuthority.getAttachmentPublicUri(slide.getUri());

Wyświetl plik

@ -192,7 +192,7 @@ public class MediaDatabase extends Database {
this.outgoing = outgoing;
}
public static MediaRecord from(@NonNull Context context, @NonNull Cursor cursor) {
public static MediaRecord from(@NonNull Cursor cursor) {
AttachmentDatabase attachmentDatabase = SignalDatabase.attachments();
List<DatabaseAttachment> attachments = attachmentDatabase.getAttachments(cursor);
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.RECIPIENT_ID)));

Wyświetl plik

@ -75,7 +75,7 @@ public final class GroupedThreadMediaLoader extends AsyncTaskLoader<GroupedThrea
try (Cursor cursor = ThreadMediaLoader.createThreadMediaCursor(context, threadId, mediaType, sorting)) {
while (cursor != null && cursor.moveToNext()) {
mediaGrouping.add(MediaDatabase.MediaRecord.from(context, cursor));
mediaGrouping.add(MediaDatabase.MediaRecord.from(cursor));
}
}

Wyświetl plik

@ -28,7 +28,6 @@ import androidx.recyclerview.widget.RecyclerView;
import com.codewaves.stickyheadergrid.StickyHeaderGridLayoutManager;
import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.MediaPreviewActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
import org.thoughtcrime.securesms.components.menu.ActionItem;
@ -38,6 +37,7 @@ import org.thoughtcrime.securesms.components.voice.VoiceNotePlaybackState;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.database.loaders.GroupedThreadMediaLoader;
import org.thoughtcrime.securesms.database.loaders.MediaLoader;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.mms.PartAuthority;
import org.thoughtcrime.securesms.util.BottomOffsetDecoration;
@ -46,6 +46,7 @@ import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import java.util.Arrays;
import java.util.Objects;
public final class MediaOverviewPageFragment extends Fragment
implements MediaGalleryAllAdapter.ItemClickListener,
@ -236,18 +237,19 @@ public final class MediaOverviewPageFragment extends Fragment
DatabaseAttachment attachment = mediaRecord.getAttachment();
if (MediaUtil.isVideo(attachment) || MediaUtil.isImage(attachment)) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.putExtra(MediaPreviewActivity.DATE_EXTRA, mediaRecord.getDate());
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, mediaRecord.getAttachment().getSize());
intent.putExtra(MediaPreviewActivity.THREAD_ID_EXTRA, threadId);
intent.putExtra(MediaPreviewActivity.LEFT_IS_RECENT_EXTRA, true);
intent.putExtra(MediaPreviewActivity.HIDE_ALL_MEDIA_EXTRA, true);
intent.putExtra(MediaPreviewActivity.SHOW_THREAD_EXTRA, threadId == MediaDatabase.ALL_THREADS);
intent.putExtra(MediaPreviewActivity.SORTING_EXTRA, sorting.ordinal());
intent.putExtra(MediaPreviewActivity.IS_VIDEO_GIF, attachment.isVideoGif());
intent.setDataAndType(mediaRecord.getAttachment().getUri(), mediaRecord.getContentType());
context.startActivity(intent);
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
threadId,
mediaRecord.getDate(),
Objects.requireNonNull(mediaRecord.getAttachment().getUri()),
mediaRecord.getContentType(),
mediaRecord.getAttachment().getSize(),
mediaRecord.getAttachment().getCaption(),
true,
true,
threadId == MediaDatabase.ALL_THREADS,
sorting.ordinal(),
attachment.isVideoGif());
context.startActivity(MediaIntentFactory.create(context, args));
} else {
if (!MediaUtil.isAudio(attachment)) {
showFileExternally(context, mediaRecord);

Wyświetl plik

@ -0,0 +1,66 @@
package org.thoughtcrime.securesms.mediapreview
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import org.thoughtcrime.securesms.MediaPreviewActivity
import org.thoughtcrime.securesms.util.FeatureFlags
object MediaIntentFactory {
private const val ARGS_KEY = "args"
const val NOT_IN_A_THREAD = -2
const val UNKNOWN_TIMESTAMP = -2
const val THREAD_ID_EXTRA = "thread_id"
const val DATE_EXTRA = "date"
const val SIZE_EXTRA = "size"
const val CAPTION_EXTRA = "caption"
const val LEFT_IS_RECENT_EXTRA = "left_is_recent"
const val HIDE_ALL_MEDIA_EXTRA = "came_from_all_media"
const val SHOW_THREAD_EXTRA = "show_thread"
const val SORTING_EXTRA = "sorting"
const val IS_VIDEO_GIF = "is_video_gif"
@Parcelize
data class MediaPreviewArgs(
val threadId: Long,
val date: Long,
val initialMediaUri: Uri,
val initialMediaType: String,
val initialMediaSize: Long,
val initialCaption: String? = null,
val leftIsRecent: Boolean = false,
val hideAllMedia: Boolean = false,
val showThread: Boolean= false,
val sorting: Int,
val isVideoGif: Boolean
) : Parcelable
@JvmStatic
fun requireArguments(bundle: Bundle): MediaPreviewArgs = bundle.getParcelable(ARGS_KEY)!!
@JvmStatic
fun create(context: Context, args: MediaPreviewArgs): Intent {
return if (FeatureFlags.mediaPreviewV2()) {
val intent = Intent(context, MediaPreviewV2Activity::class.java)
intent.putExtra(ARGS_KEY, args)
return intent
} else {
val intent = Intent(context, MediaPreviewActivity::class.java).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
setDataAndType(args.initialMediaUri, args.initialMediaType)
putExtra(THREAD_ID_EXTRA, args.threadId)
putExtra(DATE_EXTRA, args.date)
putExtra(SIZE_EXTRA, args.initialMediaSize)
putExtra(CAPTION_EXTRA, args.initialCaption)
putExtra(IS_VIDEO_GIF, args.isVideoGif)
putExtra(LEFT_IS_RECENT_EXTRA, args.leftIsRecent)
}
return intent
}
}
}

Wyświetl plik

@ -0,0 +1,65 @@
package org.thoughtcrime.securesms.mediapreview
import android.net.Uri
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import org.signal.core.util.logging.Log
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.attachments.AttachmentId
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.MediaDatabase
import org.thoughtcrime.securesms.database.MediaDatabase.Sorting
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.media
import org.thoughtcrime.securesms.mms.PartAuthority
/**
* Repository for accessing the attachments in the encrypted database.
*/
class MediaPreviewRepository {
companion object {
private val TAG: String = Log.tag(MediaPreviewRepository::class.java)
}
/**
* Accessor for database attachments.
* @param startingUri the initial position to select from
* @param threadId the thread to select from
* @param sorting the ordering of the results
* @param limit the maximum quantity of the results
*/
fun getAttachments(startingUri: Uri, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable<List<DatabaseAttachment>> {
return Single.fromCallable {
val cursor = media.getGalleryMediaForThread(threadId, sorting)
val acc = mutableListOf<DatabaseAttachment>()
var attachmentUri: Uri? = null
while (cursor.moveToNext()) {
val attachmentId = AttachmentId(cursor.requireLong(AttachmentDatabase.ROW_ID), cursor.requireLong(AttachmentDatabase.UNIQUE_ID))
attachmentUri = PartAuthority.getAttachmentDataUri(attachmentId)
if (attachmentUri == startingUri) {
break
}
}
if (attachmentUri == startingUri) {
for (i in 0..limit) {
val element = MediaDatabase.MediaRecord.from(cursor).attachment
if (element != null) {
acc.add(element)
}
if (!cursor.isLast) {
cursor.moveToNext()
} else {
break
}
}
acc.toList()
} else {
Log.e(TAG, "Could not find $startingUri in thread $threadId")
emptyList()
}
}.subscribeOn(Schedulers.io()).toFlowable()
}
}

Wyświetl plik

@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.mediapreview
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.commit
import org.thoughtcrime.securesms.R
class MediaPreviewV2Activity : AppCompatActivity(R.layout.activity_mediapreview_v2) {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState == null) {
val bundle = Bundle()
val args = MediaIntentFactory.requireArguments(intent.extras!!)
bundle.putParcelable(MediaPreviewV2Fragment.ARGS_KEY, args)
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fragment_container_view, MediaPreviewV2Fragment::class.java, bundle, FRAGMENT_TAG)
}
}
}
companion object {
private const val FRAGMENT_TAG = "media_preview_fragment_v2"
private const val NOT_IN_A_THREAD = -2
const val THREAD_ID_EXTRA = "thread_id"
}
}

Wyświetl plik

@ -0,0 +1,57 @@
package org.thoughtcrime.securesms.mediapreview
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.R
import org.thoughtcrime.securesms.animation.DepthPageTransformer
import org.thoughtcrime.securesms.components.ViewBinderDelegate
import org.thoughtcrime.securesms.database.MediaDatabase
import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding
import org.thoughtcrime.securesms.util.LifecycleDisposable
class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events {
private val TAG = Log.tag(MediaPreviewV2Fragment::class.java)
private val lifecycleDisposable = LifecycleDisposable()
private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind)
private val viewModel: MediaPreviewV2ViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.mediaPager.offscreenPageLimit = 1
binding.mediaPager.setPageTransformer(DepthPageTransformer())
lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe {
if (it.loadState == MediaPreviewV2State.LoadState.READY) {
binding.mediaPager.adapter = PreviewMediaAdapter(this, it.attachments)
}
}
initializeViewModel()
}
private fun initializeViewModel() {
val args = MediaIntentFactory.requireArguments(requireArguments())
val sorting = MediaDatabase.Sorting.values()[args.sorting]
viewModel.fetchAttachments(args.initialMediaUri, args.threadId, sorting)
}
override fun singleTapOnMedia(): Boolean {
Log.d(TAG, "singleTapOnMedia()")
return true
}
override fun mediaNotAvailable() {
Log.d(TAG, "mediaNotAvailable()")
}
override fun onMediaReady() {
Log.d(TAG, "onMediaReady()")
}
companion object {
val ARGS_KEY: String = "args"
}
}

Wyświetl plik

@ -0,0 +1,10 @@
package org.thoughtcrime.securesms.mediapreview
import org.thoughtcrime.securesms.attachments.Attachment
data class MediaPreviewV2State(
val attachments: List<Attachment> = emptyList(),
val loadState: LoadState = LoadState.INIT
) {
enum class LoadState { INIT, READY, }
}

Wyświetl plik

@ -0,0 +1,30 @@
package org.thoughtcrime.securesms.mediapreview
import android.net.Uri
import androidx.lifecycle.ViewModel
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.kotlin.plusAssign
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.database.MediaDatabase
import org.thoughtcrime.securesms.util.rx.RxStore
class MediaPreviewV2ViewModel : ViewModel() {
private val TAG = Log.tag(MediaPreviewV2ViewModel::class.java)
private val store = RxStore(MediaPreviewV2State())
private val disposables = CompositeDisposable()
private val repository: MediaPreviewRepository = MediaPreviewRepository()
val state: Flowable<MediaPreviewV2State> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
fun fetchAttachments(startingUri: Uri, threadId: Long, sorting: MediaDatabase.Sorting) {
disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { attachments, oldState ->
oldState.copy(attachments = attachments, loadState = MediaPreviewV2State.LoadState.READY)
}
}
override fun onCleared() {
disposables.dispose()
}
}

Wyświetl plik

@ -47,14 +47,14 @@ public class MediaPreviewViewModel extends ViewModel {
cursor.moveToPosition(activePosition);
MediaRecord activeRecord = MediaRecord.from(context, cursor);
MediaRecord activeRecord = MediaRecord.from(cursor);
LinkedList<Media> rail = new LinkedList<>();
Media activeMedia = toMedia(activeRecord);
if (activeMedia != null) rail.add(activeMedia);
while (cursor.moveToPrevious()) {
MediaRecord record = MediaRecord.from(context, cursor);
MediaRecord record = MediaRecord.from(cursor);
if (record.getAttachment().getMmsId() == activeRecord.getAttachment().getMmsId()) {
Media media = toMedia(record);
if (media != null) rail.addFirst(media);
@ -66,7 +66,7 @@ public class MediaPreviewViewModel extends ViewModel {
cursor.moveToPosition(activePosition);
while (cursor.moveToNext()) {
MediaRecord record = MediaRecord.from(context, cursor);
MediaRecord record = MediaRecord.from(cursor);
if (record.getAttachment().getMmsId() == activeRecord.getAttachment().getMmsId()) {
Media media = toMedia(record);
if (media != null) rail.addLast(media);

Wyświetl plik

@ -0,0 +1,39 @@
package org.thoughtcrime.securesms.mediapreview
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import org.thoughtcrime.securesms.attachments.Attachment
import org.thoughtcrime.securesms.util.MediaUtil
class PreviewMediaAdapter(val fragment: Fragment, val items: List<Attachment>) : FragmentStateAdapter(fragment) {
var autoPlayPosition = -1
override fun getItemCount(): Int {
return items.count()
}
override fun createFragment(position: Int): Fragment {
val attachment: Attachment = items[position]
val contentType = attachment.contentType
val args = bundleOf(
MediaPreviewFragment.DATA_URI to attachment.uri,
MediaPreviewFragment.DATA_CONTENT_TYPE to contentType,
MediaPreviewFragment.DATA_SIZE to attachment.size,
MediaPreviewFragment.AUTO_PLAY to (position == autoPlayPosition),
MediaPreviewFragment.VIDEO_GIF to attachment.isVideoGif,
)
val fragment = if (MediaUtil.isVideo(contentType)) {
VideoMediaPreviewFragment()
} else if (MediaUtil.isImageType(contentType)) {
ImageMediaPreviewFragment()
} else {
throw AssertionError("Unexpected media type: $contentType")
}
fragment.arguments = args
return fragment
}
}

Wyświetl plik

@ -52,8 +52,10 @@ import org.thoughtcrime.securesms.components.location.SignalMapView;
import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.conversation.MessageSendType;
import org.thoughtcrime.securesms.database.AttachmentDatabase;
import org.thoughtcrime.securesms.database.MediaDatabase;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.maps.PlacePickerActivity;
import org.thoughtcrime.securesms.mediapreview.MediaIntentFactory;
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity;
import org.thoughtcrime.securesms.payments.create.CreatePaymentFragmentArgs;
import org.thoughtcrime.securesms.payments.preferences.PaymentsActivity;
@ -469,13 +471,19 @@ public class AttachmentManager {
private void previewImageDraft(final @NonNull Slide slide) {
if (MediaPreviewActivity.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
Intent intent = new Intent(context, MediaPreviewActivity.class);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.putExtra(MediaPreviewActivity.SIZE_EXTRA, slide.asAttachment().getSize());
intent.putExtra(MediaPreviewActivity.CAPTION_EXTRA, slide.getCaption().orElse(null));
intent.setDataAndType(slide.getUri(), slide.getContentType());
context.startActivity(intent);
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
MediaIntentFactory.NOT_IN_A_THREAD,
MediaIntentFactory.UNKNOWN_TIMESTAMP,
slide.getUri(),
slide.getContentType(),
slide.asAttachment().getSize(),
slide.getCaption().orElse(null),
false,
false,
false,
MediaDatabase.Sorting.Newest.ordinal(),
slide.isVideoGif());
context.startActivity(MediaIntentFactory.create(context, args));
}
}

Wyświetl plik

@ -103,6 +103,7 @@ public final class FeatureFlags {
private static final String CDS_V2_COMPAT = "android.cdsV2Compat.4";
public static final String STORIES_LOCALE = "android.stories.locale";
private static final String HIDE_CONTACTS = "android.hide.contacts";
public static final String MEDIA_PREVIEW_V2 = "android.mediaPreviewV2";
/**
* We will only store remote values for flags in this set. If you want a flag to be controllable
@ -157,7 +158,8 @@ public final class FeatureFlags {
SMS_EXPORTER,
CDS_V2_COMPAT,
STORIES_LOCALE,
HIDE_CONTACTS
HIDE_CONTACTS,
MEDIA_PREVIEW_V2
);
@VisibleForTesting
@ -220,7 +222,8 @@ public final class FeatureFlags {
RECIPIENT_MERGE_V2,
CDS_V2_LOAD_TEST,
CDS_V2_COMPAT,
STORIES
STORIES,
MEDIA_PREVIEW_V2
);
/**
@ -565,6 +568,13 @@ public final class FeatureFlags {
return getBoolean(HIDE_CONTACTS, false);
}
/**
* Whether or not we should use the new media preview fragment implementation.
*/
public static boolean mediaPreviewV2() {
return getBoolean(MEDIA_PREVIEW_V2, false);
}
/** Only for rendering debug info. */
public static synchronized @NonNull Map<String, Object> getMemoryValues() {
return new TreeMap<>(REMOTE_VALUES);

Wyświetl plik

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.fragment.app.FragmentContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />

Wyświetl plik

@ -0,0 +1,13 @@
<?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">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/media_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true" />
</FrameLayout>