From 4f3910e3ae87e2eaa9d4b52bc72e7e50b055ff6e Mon Sep 17 00:00:00 2001 From: Nicholas Date: Mon, 3 Oct 2022 15:10:30 -0400 Subject: [PATCH] Add toolbar to MediaPreviewV2 implementation. --- .../mediapreview/MediaPreviewRepository.kt | 7 +- .../mediapreview/MediaPreviewV2Activity.kt | 3 - ...diaAdapter.kt => MediaPreviewV2Adapter.kt} | 12 ++- .../mediapreview/MediaPreviewV2Fragment.kt | 82 ++++++++++++++++++- .../mediapreview/MediaPreviewV2State.kt | 8 +- .../mediapreview/MediaPreviewV2ViewModel.kt | 19 ++++- .../res/layout/fragment_media_preview_v2.xml | 29 ++++++- 7 files changed, 139 insertions(+), 21 deletions(-) rename app/src/main/java/org/thoughtcrime/securesms/mediapreview/{PreviewMediaAdapter.kt => MediaPreviewV2Adapter.kt} (77%) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt index ee0c94b4d5..25c83262b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewRepository.kt @@ -7,7 +7,6 @@ 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 @@ -29,11 +28,11 @@ class MediaPreviewRepository { * @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> { + fun getAttachments(startingUri: Uri, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable> { return Single.fromCallable { val cursor = media.getGalleryMediaForThread(threadId, sorting) - val acc = mutableListOf() + val acc = mutableListOf() var attachmentUri: Uri? = null while (cursor.moveToNext()) { val attachmentId = AttachmentId(cursor.requireLong(AttachmentDatabase.ROW_ID), cursor.requireLong(AttachmentDatabase.UNIQUE_ID)) @@ -45,7 +44,7 @@ class MediaPreviewRepository { if (attachmentUri == startingUri) { for (i in 0..limit) { - val element = MediaDatabase.MediaRecord.from(cursor).attachment + val element = MediaDatabase.MediaRecord.from(cursor) if (element != null) { acc.add(element) } 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 3735f8fc00..cc1f34a0c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Activity.kt @@ -21,8 +21,5 @@ class MediaPreviewV2Activity : AppCompatActivity(R.layout.activity_mediapreview_ 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" } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/PreviewMediaAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt similarity index 77% rename from app/src/main/java/org/thoughtcrime/securesms/mediapreview/PreviewMediaAdapter.kt rename to app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt index 781a461538..3992b53842 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/PreviewMediaAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Adapter.kt @@ -6,8 +6,9 @@ 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) : FragmentStateAdapter(fragment) { - var autoPlayPosition = -1 +class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragment) { + private var items: List = listOf() + private var autoPlayPosition = -1 override fun getItemCount(): Int { return items.count() @@ -36,4 +37,11 @@ class PreviewMediaAdapter(val fragment: Fragment, val items: List) : return fragment } + + fun updateBackingItems(newItems: Collection) { + if (newItems != items) { + items = newItems.toList() + notifyDataSetChanged() + } + } } 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 3a22843c4c..e89cc7c77e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2Fragment.kt @@ -1,9 +1,11 @@ package org.thoughtcrime.securesms.mediapreview +import android.content.Context import android.os.Bundle import android.view.View import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels +import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.R @@ -11,7 +13,11 @@ 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.recipients.Recipient +import org.thoughtcrime.securesms.util.DateUtils +import org.thoughtcrime.securesms.util.FullscreenHelper import org.thoughtcrime.securesms.util.LifecycleDisposable +import java.util.Locale class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events { private val TAG = Log.tag(MediaPreviewV2Fragment::class.java) @@ -20,24 +26,92 @@ class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), Med private val binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind) private val viewModel: MediaPreviewV2ViewModel by viewModels() + private lateinit var fullscreenHelper: FullscreenHelper + + override fun onAttach(context: Context) { + super.onAttach(context) + fullscreenHelper = FullscreenHelper(requireActivity()) + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initializeViewModel() 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) + val adapter = MediaPreviewV2Adapter(this) + binding.mediaPager.adapter = adapter + binding.mediaPager.registerOnPageChangeCallback(object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + viewModel.setCurrentPage(position) } + }) + initializeFullScreenUi() + lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe { + bindCurrentState(it) } - initializeViewModel() + } + + private fun initializeFullScreenUi() { + fullscreenHelper.configureToolbarLayout(binding.toolbarCutoutSpacer, binding.toolbar) + fullscreenHelper.hideSystemUI() } private fun initializeViewModel() { val args = MediaIntentFactory.requireArguments(requireArguments()) + viewModel.setShowThread(args.showThread) val sorting = MediaDatabase.Sorting.values()[args.sorting] viewModel.fetchAttachments(args.initialMediaUri, args.threadId, sorting) } + private fun bindCurrentState(currentState: MediaPreviewV2State) { + when (currentState.loadState) { + MediaPreviewV2State.LoadState.READY -> bindReadyState(currentState) + // INIT, else -> no-op + } + } + + private fun bindReadyState(currentState: MediaPreviewV2State) { + (binding.mediaPager.adapter as MediaPreviewV2Adapter).updateBackingItems(currentState.mediaRecords.mapNotNull { it.attachment }) + val currentItem: MediaDatabase.MediaRecord = currentState.mediaRecords[currentState.position] + binding.toolbar.title = getTitleText(currentItem, currentState.showThread) + binding.toolbar.subtitle = getSubTitleText(currentItem) + } + + private fun getTitleText(mediaRecord: MediaDatabase.MediaRecord, showThread: Boolean): String { + val recipient: Recipient = Recipient.live(mediaRecord.recipientId).get() + val defaultFromString: String = if (mediaRecord.isOutgoing) { + getString(R.string.MediaPreviewActivity_you) + } else { + recipient.getDisplayName(requireContext()) + } + if (!showThread) { + return defaultFromString + } + + val threadRecipient = Recipient.live(mediaRecord.threadRecipientId).get() + return if (mediaRecord.isOutgoing) { + if (threadRecipient.isSelf) { + getString(R.string.note_to_self) + } else { + getString(R.string.MediaPreviewActivity_you_to_s, threadRecipient.getDisplayName(requireContext())) + } + } else { + if (threadRecipient.isGroup) { + getString(R.string.MediaPreviewActivity_s_to_s, defaultFromString, threadRecipient.getDisplayName(requireContext())) + } else { + getString(R.string.MediaPreviewActivity_s_to_you, defaultFromString) + } + } + } + + private fun getSubTitleText(mediaRecord: MediaDatabase.MediaRecord): String = + if (mediaRecord.date > 0) { + DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), mediaRecord.date) + } else { + getString(R.string.MediaPreviewActivity_draft) + } + override fun singleTapOnMedia(): Boolean { Log.d(TAG, "singleTapOnMedia()") return true 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 ef873fc32f..062468d932 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2State.kt @@ -1,10 +1,12 @@ package org.thoughtcrime.securesms.mediapreview -import org.thoughtcrime.securesms.attachments.Attachment +import org.thoughtcrime.securesms.database.MediaDatabase data class MediaPreviewV2State( - val attachments: List = emptyList(), - val loadState: LoadState = LoadState.INIT + val mediaRecords: List = emptyList(), + val loadState: LoadState = LoadState.INIT, + val position: Int = 0, + val showThread: Boolean = false ) { enum class LoadState { INIT, 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 6de2873d91..247d6cf0fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediapreview/MediaPreviewV2ViewModel.kt @@ -19,8 +19,23 @@ class MediaPreviewV2ViewModel : ViewModel() { val state: Flowable = 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) + disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { mediaRecords: List, oldState: MediaPreviewV2State -> + oldState.copy( + mediaRecords = mediaRecords, + loadState = MediaPreviewV2State.LoadState.READY + ) + } + } + + fun setShowThread(value: Boolean) { + store.update { oldState -> + oldState.copy(showThread = value) + } + } + + fun setCurrentPage(position: Int) { + store.update { oldState -> + oldState.copy(position = position) } } 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 02f8c6defa..6657aba085 100644 --- a/app/src/main/res/layout/fragment_media_preview_v2.xml +++ b/app/src/main/res/layout/fragment_media_preview_v2.xml @@ -1,9 +1,32 @@ - + android:layout_height="match_parent" + android:background="@color/core_grey_95" + android:theme="@style/TextSecure.MediaPreview"> + + + + + + +