diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java index 2e93f72ca..e374e3f9b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/ImageMediaPreviewFragment.java @@ -5,7 +5,6 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; import androidx.annotation.Nullable; @@ -16,7 +15,7 @@ import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.util.MediaUtil; public final class ImageMediaPreviewFragment extends MediaPreviewFragment { - private View bottomBarControlView; + private MediaPreviewPlayerControlView bottomBarControlView; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -41,26 +40,24 @@ public final class ImageMediaPreviewFragment extends MediaPreviewFragment { zoomingImageView.setOnClickListener(v -> events.singleTapOnMedia()); - bottomBarControlView = getLayoutInflater().inflate(R.layout.image_media_preview_bottom_bar, null); return zoomingImageView; } @Override - public void setShareButtonListener(View.OnClickListener listener) { - ImageButton forwardButton = bottomBarControlView.findViewById(R.id.image_preview_forward); - forwardButton.setOnClickListener(listener); - + public void cleanUp() { + bottomBarControlView = null; } @Override - public void setForwardButtonListener(View.OnClickListener listener) { - ImageButton shareButton = bottomBarControlView.findViewById(R.id.image_preview_share); - shareButton.setOnClickListener(listener); - } + public void pause() {} - @Nullable @Override - public View getBottomBarControls() { + public ViewGroup getBottomBarControls() { return bottomBarControlView; } + + @Override + public void setBottomButtonControls(MediaPreviewPlayerControlView playerControlView) { + bottomBarControlView = playerControlView; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java index 81d99a8c9..03731c983 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewFragment.java @@ -3,14 +3,12 @@ package org.thoughtcrime.securesms.mediapreview; import android.content.Context; import android.net.Uri; import android.os.Bundle; -import android.view.View; +import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import com.google.android.exoplayer2.ui.PlayerControlView; - import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.AttachmentId; import org.thoughtcrime.securesms.database.SignalDatabase; @@ -81,15 +79,10 @@ public abstract class MediaPreviewFragment extends Fragment { checkMediaStillAvailable(); } - public void cleanUp() { - } - - public void pause() { - } - - abstract public void setShareButtonListener(View.OnClickListener listener); - abstract public void setForwardButtonListener(View.OnClickListener listener); - abstract public @Nullable View getBottomBarControls(); + public abstract void cleanUp(); + public abstract void pause(); + public abstract ViewGroup getBottomBarControls(); + public abstract void setBottomButtonControls(MediaPreviewPlayerControlView playerControlView); private void checkMediaStillAvailable() { if (attachmentId == null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewPlayerControlView.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewPlayerControlView.kt new file mode 100644 index 000000000..2fa682afa --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewPlayerControlView.kt @@ -0,0 +1,58 @@ +package org.thoughtcrime.securesms.mediapreview + +import android.content.Context +import android.util.AttributeSet +import android.widget.ImageButton +import android.widget.LinearLayout +import androidx.recyclerview.widget.RecyclerView +import com.google.android.exoplayer2.ui.PlayerControlView +import org.thoughtcrime.securesms.R +import org.thoughtcrime.securesms.util.MediaUtil + +/** + * The bottom bar for the media preview. This includes the standard seek bar as well as playback controls, + * but adds forward and share buttons as well as a recyclerview that can be populated with a rail of thumbnails. + */ +class MediaPreviewPlayerControlView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + playbackAttrs: AttributeSet? = null +) : PlayerControlView(context, attrs, defStyleAttr, playbackAttrs) { + + val recyclerView: RecyclerView = findViewById(R.id.media_preview_album_rail) + private val durationBar: LinearLayout = findViewById(R.id.exo_duration_viewgroup) + private val videoControls: LinearLayout = findViewById(R.id.exo_button_viewgroup) + private val shareButton: ImageButton = findViewById(R.id.exo_share) + private val forwardButton: ImageButton = findViewById(R.id.exo_forward) + + enum class MediaMode { + IMAGE, VIDEO; + + companion object { + @JvmStatic + fun fromString(contentType: String): MediaMode { + if (MediaUtil.isVideo(contentType)) return VIDEO + if (MediaUtil.isImageType(contentType)) return IMAGE + throw IllegalArgumentException("Unknown content type: $contentType") + } + } + } + + init { + setShowPreviousButton(false) + setShowNextButton(false) + showShuffleButton = false + showVrButton = false + showTimeoutMs = -1 + } + + fun setVisibility(mediaMode: MediaMode) { + durationBar.visibility = if (mediaMode == MediaMode.VIDEO) VISIBLE else GONE + videoControls.visibility = if (mediaMode == MediaMode.VIDEO) VISIBLE else INVISIBLE + } + + fun setShareButtonListener(listener: OnClickListener?) = shareButton.setOnClickListener(listener) + + fun setForwardButtonListener(listener: OnClickListener?) = forwardButton.setOnClickListener(listener) +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt index 2e1b31104..82b535207 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt @@ -20,7 +20,7 @@ class MediaPreviewV2Activity : AppCompatActivity(R.layout.activity_mediapreview_ super.onCreate(savedInstanceState) setTheme(R.style.TextSecure_MediaPreview) if (Build.VERSION.SDK_INT >= 21) { - val systemBarColor = ContextCompat.getColor(this, R.color.media_preview_bar_background) + val systemBarColor = ContextCompat.getColor(this, R.color.signal_dark_colorSurface) window.statusBarColor = systemBarColor window.navigationBarColor = systemBarColor } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt index 53a901890..3e5132cdd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -18,7 +18,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import androidx.viewpager2.widget.ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.google.android.material.appbar.MaterialToolbar @@ -72,12 +72,10 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med super.onViewCreated(view, savedInstanceState) val args = MediaIntentFactory.requireArguments(requireArguments()) - initializeViewModel(args) initializeToolbar(binding.toolbar) initializeViewPager() initializeFullScreenUi() - initializeAlbumRail() anchorMarginsToBottomInsets(binding.mediaPreviewDetailsContainer) lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { bindCurrentState(it) @@ -92,10 +90,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med requireActivity().finish() }.show() } - - viewModel.setShowThread(args.showThread) - viewModel.setAlwaysShowAlbumRail(args.allMediaInRail) - viewModel.setLeftIsRecent(args.leftIsRecent) + viewModel.initialize(args.showThread, args.allMediaInRail, args.leftIsRecent) val sorting = MediaDatabase.Sorting.deserialize(args.sorting) viewModel.fetchAttachments(PartAuthority.requireAttachmentId(args.initialMediaUri), args.threadId, sorting) } @@ -105,6 +100,8 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med requireActivity().onBackPressed() } + toolbar.setTitleTextAppearance(requireContext(), R.style.Signal_Text_TitleMedium) + toolbar.setSubtitleTextAppearance(requireContext(), R.style.Signal_Text_BodyMedium) binding.toolbar.inflateMenu(R.menu.media_preview) } @@ -123,10 +120,9 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med }) } - private fun initializeAlbumRail() { - binding.mediaPreviewAlbumRail.itemAnimator = null // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682 - binding.mediaPreviewAlbumRail.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false) - binding.mediaPreviewAlbumRail.adapter = MediaRailAdapter( + private fun initializeAlbumRail(recyclerView: RecyclerView, albumThumbnailMedia: List, albumPosition: Int) { + recyclerView.itemAnimator = null // Or can crash when set to INVISIBLE while animating by FullscreenHelper https://issuetracker.google.com/issues/148720682 + val mediaRailAdapter = MediaRailAdapter( GlideApp.with(this), object : MediaRailAdapter.RailItemListener { override fun onRailItemClicked(distanceFromActive: Int) { @@ -139,6 +135,9 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med }, false ) + mediaRailAdapter.setMedia(albumThumbnailMedia, albumPosition) + recyclerView.adapter = mediaRailAdapter + recyclerView.smoothScrollToPosition(albumPosition) } private fun initializeFullScreenUi() { @@ -152,21 +151,43 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med return } when (currentState.loadState) { - MediaPreviewV2State.LoadState.READY -> bindReadyState(currentState) - MediaPreviewV2State.LoadState.LOADED -> { - bindReadyState(currentState) - bindLoadedState(currentState) - } + MediaPreviewV2State.LoadState.DATA_LOADED -> bindDataLoadedState(currentState) + MediaPreviewV2State.LoadState.MEDIA_READY -> bindMediaReadyState(currentState) else -> null } } - private fun bindReadyState(currentState: MediaPreviewV2State) { + private fun bindDataLoadedState(currentState: MediaPreviewV2State) { (binding.mediaPager.adapter as MediaPreviewV2Adapter).updateBackingItems(currentState.mediaRecords.mapNotNull { it.attachment }) - if (binding.mediaPager.currentItem != currentState.position) { - binding.mediaPager.setCurrentItem(currentState.position, false) + val currentPosition = currentState.position + if (binding.mediaPager.currentItem != currentPosition) { + binding.mediaPager.setCurrentItem(currentPosition, false) } - val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position] + } + + /** + * These are binding steps that need a reference to the actual fragment within the pager. + * This is not available until after a page has been chosen by the ViewPager, and we receive the + * {@link OnPageChangeCallback}. + */ + private fun bindMediaReadyState(currentState: MediaPreviewV2State) { + val currentPosition = currentState.position + val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentPosition] + + // pause all other fragments + childFragmentManager.fragments.map { fragment -> + if (fragment.tag != "f$currentPosition") { + (fragment as? MediaPreviewFragment)?.pause() + } + } + + val mediaType: MediaPreviewPlayerControlView.MediaMode = if (currentItem.attachment?.isVideoGif == true) { + MediaPreviewPlayerControlView.MediaMode.IMAGE + } else { + MediaPreviewPlayerControlView.MediaMode.fromString(currentItem.contentType) + } + binding.mediaPreviewPlaybackControls.setVisibility(mediaType) + binding.toolbar.title = getTitleText(currentItem, currentState.showThread) binding.toolbar.subtitle = getSubTitleText(currentItem) @@ -184,16 +205,6 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } return@setOnMenuItemClickListener true } - } - - /** - * These are binding steps that need a reference to the actual fragment within the pager. - * This is not available until after a page has been chosen by the ViewPager, and we receive the - * {@link OnPageChangeCallback}. - */ - private fun bindLoadedState(currentState: MediaPreviewV2State) { - val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position] - val albumThumbnailMedia = if (currentState.allMediaInAlbumRail) { currentState.mediaRecords.map { it.toMedia() } } else { @@ -201,11 +212,10 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med .filter { it.attachment != null && it.attachment!!.mmsId == currentItem.attachment?.mmsId } .map { it.toMedia() } } + val caption = currentItem.attachment?.caption - binding.mediaPreviewAlbumRail.visibility = if (albumThumbnailMedia.size <= 1) View.GONE else View.VISIBLE - (binding.mediaPreviewAlbumRail.adapter as MediaRailAdapter).setMedia(albumThumbnailMedia, currentState.position) - binding.mediaPreviewAlbumRail.smoothScrollToPosition(currentState.position) + val albumRailEnabled = albumThumbnailMedia.size > 1 if (caption != null) { binding.mediaPreviewCaption.text = caption @@ -214,24 +224,21 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med binding.mediaPreviewCaption.visibility = View.GONE } - val fragmentTag = "f${currentState.position}" - val currentFragment: MediaPreviewFragment? = childFragmentManager.findFragmentByTag(fragmentTag) as? MediaPreviewFragment - val playbackControls: View? = currentFragment?.bottomBarControls - if (albumThumbnailMedia.size <= 1 && caption == null && playbackControls == null) { - binding.mediaPreviewDetailsContainer.visibility = View.GONE - } else { - binding.mediaPreviewDetailsContainer.visibility = View.VISIBLE + binding.mediaPreviewPlaybackControls.setShareButtonListener { share(currentItem) } + binding.mediaPreviewPlaybackControls.setForwardButtonListener { forward(currentItem) } + + val albumRail: RecyclerView = binding.mediaPreviewPlaybackControls.findViewById(R.id.media_preview_album_rail) + if (albumRailEnabled) { + val albumPosition = albumThumbnailMedia.indexOfFirst { it?.uri == currentItem.attachment?.uri } + initializeAlbumRail(albumRail, albumThumbnailMedia, albumPosition) } - binding.mediaPreviewPlaybackControlsContainer.removeAllViews() - if (playbackControls != null) { - val params = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) - playbackControls.layoutParams = params - binding.mediaPreviewPlaybackControlsContainer.addView(playbackControls) - } - currentFragment?.setShareButtonListener { share(currentItem) } - currentFragment?.setForwardButtonListener { forward(currentItem) } + albumRail.visibility = if (albumRailEnabled) View.VISIBLE else View.GONE + val currentFragment: MediaPreviewFragment? = getMediaPreviewFragmentFromChildFragmentManager(currentPosition) + currentFragment?.setBottomButtonControls(binding.mediaPreviewPlaybackControls) } + private fun getMediaPreviewFragmentFromChildFragmentManager(currentPosition: Int) = childFragmentManager.findFragmentByTag("f$currentPosition") as? MediaPreviewFragment + private fun getTitleText(mediaRecord: MediaDatabase.MediaRecord, showThread: Boolean): String { val recipient: Recipient = Recipient.live(mediaRecord.recipientId).get() val defaultFromString: String = if (mediaRecord.isOutgoing) { @@ -315,7 +322,7 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med } override fun onMediaReady() { - Log.d(TAG, "onMediaReady()") + viewModel.setMediaReady() } private fun forward(mediaItem: MediaDatabase.MediaRecord) { @@ -400,6 +407,11 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med .show() } + override fun onPause() { + super.onPause() + getMediaPreviewFragmentFromChildFragmentManager(binding.mediaPager.currentItem)?.pause() + } + companion object { const val ARGS_KEY: String = "args" } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt index e7c5e850c..13f6aa91c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt @@ -8,7 +8,7 @@ data class MediaPreviewV2State( val position: Int = 0, val showThread: Boolean = false, val allMediaInAlbumRail: Boolean = false, - val leftIsRecent: Boolean = false + val leftIsRecent: Boolean = false, ) { - enum class LoadState { INIT, READY, LOADED } + enum class LoadState { INIT, DATA_LOADED, MEDIA_READY } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt index b5ae55fc7..21712d5c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -24,46 +24,44 @@ class MediaPreviewV2ViewModel : ViewModel() { val state: Flowable = store.stateFlowable.observeOn(AndroidSchedulers.mainThread()) - fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaDatabase.Sorting) { - disposables += store.update(repository.getAttachments(startingAttachmentId, threadId, sorting)) { - result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> - if (oldState.leftIsRecent) { - oldState.copy( - position = result.initialPosition, - mediaRecords = result.records, - loadState = MediaPreviewV2State.LoadState.READY, - ) - } else { - oldState.copy( - position = result.records.size - result.initialPosition - 1, - mediaRecords = result.records.reversed(), - loadState = MediaPreviewV2State.LoadState.READY, - ) + fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaDatabase.Sorting, forceRefresh: Boolean = false) { + if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) { + disposables += store.update(repository.getAttachments(startingAttachmentId, threadId, sorting)) { + result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State -> + if (oldState.leftIsRecent) { + oldState.copy( + position = result.initialPosition, + mediaRecords = result.records, + loadState = MediaPreviewV2State.LoadState.DATA_LOADED, + ) + } else { + oldState.copy( + position = result.records.size - result.initialPosition - 1, + mediaRecords = result.records.reversed(), + loadState = MediaPreviewV2State.LoadState.DATA_LOADED, + ) + } } } } - fun setShowThread(value: Boolean) { - store.update { oldState -> - oldState.copy(showThread = value) - } - } - - fun setAlwaysShowAlbumRail(value: Boolean) { - store.update { oldState -> - oldState.copy(allMediaInAlbumRail = value) - } - } - - fun setLeftIsRecent(value: Boolean) { - store.update { oldState -> - oldState.copy(leftIsRecent = value) + fun initialize(showThread: Boolean, allMediaInAlbumRail: Boolean, leftIsRecent: Boolean) { + if (store.state.loadState == MediaPreviewV2State.LoadState.INIT) { + store.update { oldState -> + oldState.copy(showThread = showThread, allMediaInAlbumRail = allMediaInAlbumRail, leftIsRecent = leftIsRecent) + } } } fun setCurrentPage(position: Int) { store.update { oldState -> - oldState.copy(position = position, loadState = MediaPreviewV2State.LoadState.LOADED) + oldState.copy(position = position) + } + } + + fun setMediaReady() { + store.update { oldState -> + oldState.copy(loadState = MediaPreviewV2State.LoadState.MEDIA_READY) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java index 8530cd686..c0671b07d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/VideoMediaPreviewFragment.java @@ -6,7 +6,6 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageButton; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -30,8 +29,6 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { private VideoPlayer videoView; private boolean isVideoGif; - private ImageButton shareButton; - private ImageButton forwardButton; @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -90,21 +87,15 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { }); if (isVideoGif) { - videoView.hideControls(); videoView.loopForever(); } videoView.setOnClickListener(v -> events.singleTapOnMedia()); - final PlayerControlView controlView = videoView.getControlView(); - if (controlView != null) { - shareButton = controlView.findViewById(R.id.exo_share); - forwardButton = controlView.findViewById(R.id.exo_forward); - } return itemView; } private void updateSkipButtonState() { - final PlayerControlView playbackControls = getBottomBarControls(); + final PlayerControlView playbackControls = videoView.getControlView(); if (playbackControls != null) { boolean shouldShowSkipButtons = videoView.getDuration() > MINIMUM_DURATION_FOR_SKIP_MS; playbackControls.setShowFastForwardButton(shouldShowSkipButtons); @@ -149,20 +140,16 @@ public final class VideoMediaPreviewFragment extends MediaPreviewFragment { } } - @Override - public void setShareButtonListener(View.OnClickListener listener) { - shareButton.setOnClickListener(listener); - } - - @Override - public void setForwardButtonListener(View.OnClickListener listener) { - forwardButton.setOnClickListener(listener); - } - @Nullable @Override - public PlayerControlView getBottomBarControls() { - return videoView != null && !isVideoGif ? videoView.getControlView() : null; + public MediaPreviewPlayerControlView getBottomBarControls() { + return (MediaPreviewPlayerControlView) videoView.getControlView(); + } + + @Override + public void setBottomButtonControls(@NonNull MediaPreviewPlayerControlView playerControlView) { + videoView.setControlView(playerControlView); + updateSkipButtonState(); } private @NonNull Uri getUri() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index 3a5a57f0b..e4a992f4b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/app/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -19,15 +19,12 @@ package org.thoughtcrime.securesms.video; import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; -import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.core.content.res.TypedArrayKt; -import androidx.core.content.res.TypedArrayUtils; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayer; @@ -45,6 +42,7 @@ import com.google.android.exoplayer2.ui.PlayerView; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.mediapreview.MediaPreviewPlayerControlView; import org.thoughtcrime.securesms.mms.VideoSlide; import java.util.Objects; @@ -56,10 +54,10 @@ public class VideoPlayer extends FrameLayout { private static final String TAG = Log.tag(VideoPlayer.class); private final PlayerView exoView; - private final PlayerControlView exoControls; private final DefaultMediaSourceFactory mediaSourceFactory; private ExoPlayer exoPlayer; + private PlayerControlView exoControls; private Window window; private PlayerStateCallback playerStateCallback; private PlayerPositionDiscontinuityCallback playerPositionDiscontinuityCallback; @@ -233,6 +231,11 @@ public class VideoPlayer extends FrameLayout { return this.exoControls; } + public void setControlView(MediaPreviewPlayerControlView controller) { + exoControls = controller; + exoControls.setPlayer(exoPlayer); + } + public void stop() { if (this.exoPlayer != null) { exoPlayer.stop(); diff --git a/app/src/main/res/drawable/ic_chevron_right_12.xml b/app/src/main/res/drawable/ic_chevron_right_12.xml new file mode 100644 index 000000000..b63d1604e --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right_12.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_download_outline_24.xml b/app/src/main/res/drawable/ic_download_outline_24.xml new file mode 100644 index 000000000..d8e44967f --- /dev/null +++ b/app/src/main/res/drawable/ic_download_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_editmedia_outline_24.xml b/app/src/main/res/drawable/ic_editmedia_outline_24.xml new file mode 100644 index 000000000..e97bba547 --- /dev/null +++ b/app/src/main/res/drawable/ic_editmedia_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_forward_outline_24.xml b/app/src/main/res/drawable/ic_forward_outline_24.xml new file mode 100644 index 000000000..93a98f059 --- /dev/null +++ b/app/src/main/res/drawable/ic_forward_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_media_skipback_outline_24.xml b/app/src/main/res/drawable/ic_media_skipback_outline_24.xml new file mode 100644 index 000000000..3bb1e806d --- /dev/null +++ b/app/src/main/res/drawable/ic_media_skipback_outline_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_media_skipforward_outline_24.xml b/app/src/main/res/drawable/ic_media_skipforward_outline_24.xml new file mode 100644 index 000000000..61e5911ce --- /dev/null +++ b/app/src/main/res/drawable/ic_media_skipforward_outline_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_trash_outline_24.xml b/app/src/main/res/drawable/ic_trash_outline_24.xml new file mode 100644 index 000000000..72714c6e4 --- /dev/null +++ b/app/src/main/res/drawable/ic_trash_outline_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/exo_player_control_view.xml b/app/src/main/res/layout/exo_player_control_view.xml index 6e4721117..8abd2b622 100644 --- a/app/src/main/res/layout/exo_player_control_view.xml +++ b/app/src/main/res/layout/exo_player_control_view.xml @@ -5,51 +5,69 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - android:background="#CC000000" + android:background="@color/transparent" android:layoutDirection="ltr" android:orientation="vertical" tools:targetApi="28"> + android:textSize="14sp" /> - + android:textSize="14sp" /> + + @@ -66,41 +84,53 @@ android:layout_height="0dp" android:layout_weight="1.0" /> - + - + - + - + - + - + - + - + - + + + + + app:srcCompat="@drawable/ic_forward_outline_24" /> diff --git a/app/src/main/res/layout/fragment_media_preview_v2.xml b/app/src/main/res/layout/fragment_media_preview_v2.xml index 2fec133be..e4bf8514b 100644 --- a/app/src/main/res/layout/fragment_media_preview_v2.xml +++ b/app/src/main/res/layout/fragment_media_preview_v2.xml @@ -1,10 +1,11 @@ + android:background="@color/signal_dark_colorSurface"> + - - - + android:animateLayoutChanges="true" + android:background="@color/transparent" /> + diff --git a/app/src/main/res/layout/image_media_preview_bottom_bar.xml b/app/src/main/res/layout/image_media_preview_bottom_bar.xml deleted file mode 100644 index 05f671c0a..000000000 --- a/app/src/main/res/layout/image_media_preview_bottom_bar.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 46a408ff6..5443e324a 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -235,4 +235,6 @@ 52dp 76dp 20dp + 24dp + 20dp diff --git a/app/src/main/res/values/material3_colors_dark.xml b/app/src/main/res/values/material3_colors_dark.xml index 3c5ae1b01..87450d05b 100644 --- a/app/src/main/res/values/material3_colors_dark.xml +++ b/app/src/main/res/values/material3_colors_dark.xml @@ -43,5 +43,6 @@ #A3FFFFFF #EB1B1C1F + #DE1B1C1F #EB303133 \ No newline at end of file