Add toolbar to MediaPreviewV2 implementation.
This commit is contained in:
parent
79b3b9190a
commit
4f3910e3ae
7 changed files with 139 additions and 21 deletions
|
@ -7,7 +7,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.thoughtcrime.securesms.attachments.AttachmentId
|
import org.thoughtcrime.securesms.attachments.AttachmentId
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
import org.thoughtcrime.securesms.database.AttachmentDatabase
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase.Sorting
|
import org.thoughtcrime.securesms.database.MediaDatabase.Sorting
|
||||||
|
@ -29,11 +28,11 @@ class MediaPreviewRepository {
|
||||||
* @param sorting the ordering of the results
|
* @param sorting the ordering of the results
|
||||||
* @param limit the maximum quantity 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>> {
|
fun getAttachments(startingUri: Uri, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable<List<MediaDatabase.MediaRecord>> {
|
||||||
return Single.fromCallable {
|
return Single.fromCallable {
|
||||||
val cursor = media.getGalleryMediaForThread(threadId, sorting)
|
val cursor = media.getGalleryMediaForThread(threadId, sorting)
|
||||||
|
|
||||||
val acc = mutableListOf<DatabaseAttachment>()
|
val acc = mutableListOf<MediaDatabase.MediaRecord>()
|
||||||
var attachmentUri: Uri? = null
|
var attachmentUri: Uri? = null
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
val attachmentId = AttachmentId(cursor.requireLong(AttachmentDatabase.ROW_ID), cursor.requireLong(AttachmentDatabase.UNIQUE_ID))
|
val attachmentId = AttachmentId(cursor.requireLong(AttachmentDatabase.ROW_ID), cursor.requireLong(AttachmentDatabase.UNIQUE_ID))
|
||||||
|
@ -45,7 +44,7 @@ class MediaPreviewRepository {
|
||||||
|
|
||||||
if (attachmentUri == startingUri) {
|
if (attachmentUri == startingUri) {
|
||||||
for (i in 0..limit) {
|
for (i in 0..limit) {
|
||||||
val element = MediaDatabase.MediaRecord.from(cursor).attachment
|
val element = MediaDatabase.MediaRecord.from(cursor)
|
||||||
if (element != null) {
|
if (element != null) {
|
||||||
acc.add(element)
|
acc.add(element)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,5 @@ class MediaPreviewV2Activity : AppCompatActivity(R.layout.activity_mediapreview_
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val FRAGMENT_TAG = "media_preview_fragment_v2"
|
private const val FRAGMENT_TAG = "media_preview_fragment_v2"
|
||||||
private const val NOT_IN_A_THREAD = -2
|
|
||||||
|
|
||||||
const val THREAD_ID_EXTRA = "thread_id"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment
|
import org.thoughtcrime.securesms.attachments.Attachment
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil
|
import org.thoughtcrime.securesms.util.MediaUtil
|
||||||
|
|
||||||
class PreviewMediaAdapter(val fragment: Fragment, val items: List<Attachment>) : FragmentStateAdapter(fragment) {
|
class MediaPreviewV2Adapter(val fragment: Fragment) : FragmentStateAdapter(fragment) {
|
||||||
var autoPlayPosition = -1
|
private var items: List<Attachment> = listOf()
|
||||||
|
private var autoPlayPosition = -1
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
override fun getItemCount(): Int {
|
||||||
return items.count()
|
return items.count()
|
||||||
|
@ -36,4 +37,11 @@ class PreviewMediaAdapter(val fragment: Fragment, val items: List<Attachment>) :
|
||||||
|
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateBackingItems(newItems: Collection<Attachment>) {
|
||||||
|
if (newItems != items) {
|
||||||
|
items = newItems.toList()
|
||||||
|
notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package org.thoughtcrime.securesms.mediapreview
|
package org.thoughtcrime.securesms.mediapreview
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.thoughtcrime.securesms.R
|
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.components.ViewBinderDelegate
|
||||||
import org.thoughtcrime.securesms.database.MediaDatabase
|
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||||
import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding
|
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 org.thoughtcrime.securesms.util.LifecycleDisposable
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events {
|
class MediaPreviewV2Fragment : Fragment(R.layout.fragment_media_preview_v2), MediaPreviewFragment.Events {
|
||||||
private val TAG = Log.tag(MediaPreviewV2Fragment::class.java)
|
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 binding by ViewBinderDelegate(FragmentMediaPreviewV2Binding::bind)
|
||||||
private val viewModel: MediaPreviewV2ViewModel by viewModels()
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
initializeViewModel()
|
||||||
binding.mediaPager.offscreenPageLimit = 1
|
binding.mediaPager.offscreenPageLimit = 1
|
||||||
binding.mediaPager.setPageTransformer(DepthPageTransformer())
|
binding.mediaPager.setPageTransformer(DepthPageTransformer())
|
||||||
lifecycleDisposable += viewModel.state.distinctUntilChanged().observeOn(AndroidSchedulers.mainThread()).subscribe {
|
val adapter = MediaPreviewV2Adapter(this)
|
||||||
if (it.loadState == MediaPreviewV2State.LoadState.READY) {
|
binding.mediaPager.adapter = adapter
|
||||||
binding.mediaPager.adapter = PreviewMediaAdapter(this, it.attachments)
|
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() {
|
private fun initializeViewModel() {
|
||||||
val args = MediaIntentFactory.requireArguments(requireArguments())
|
val args = MediaIntentFactory.requireArguments(requireArguments())
|
||||||
|
viewModel.setShowThread(args.showThread)
|
||||||
val sorting = MediaDatabase.Sorting.values()[args.sorting]
|
val sorting = MediaDatabase.Sorting.values()[args.sorting]
|
||||||
viewModel.fetchAttachments(args.initialMediaUri, args.threadId, 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 {
|
override fun singleTapOnMedia(): Boolean {
|
||||||
Log.d(TAG, "singleTapOnMedia()")
|
Log.d(TAG, "singleTapOnMedia()")
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package org.thoughtcrime.securesms.mediapreview
|
package org.thoughtcrime.securesms.mediapreview
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment
|
import org.thoughtcrime.securesms.database.MediaDatabase
|
||||||
|
|
||||||
data class MediaPreviewV2State(
|
data class MediaPreviewV2State(
|
||||||
val attachments: List<Attachment> = emptyList(),
|
val mediaRecords: List<MediaDatabase.MediaRecord> = emptyList(),
|
||||||
val loadState: LoadState = LoadState.INIT
|
val loadState: LoadState = LoadState.INIT,
|
||||||
|
val position: Int = 0,
|
||||||
|
val showThread: Boolean = false
|
||||||
) {
|
) {
|
||||||
enum class LoadState { INIT, READY, }
|
enum class LoadState { INIT, READY, }
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,23 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||||
val state: Flowable<MediaPreviewV2State> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
val state: Flowable<MediaPreviewV2State> = store.stateFlowable.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
||||||
fun fetchAttachments(startingUri: Uri, threadId: Long, sorting: MediaDatabase.Sorting) {
|
fun fetchAttachments(startingUri: Uri, threadId: Long, sorting: MediaDatabase.Sorting) {
|
||||||
disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { attachments, oldState ->
|
disposables += store.update(repository.getAttachments(startingUri, threadId, sorting)) { mediaRecords: List<MediaDatabase.MediaRecord>, oldState: MediaPreviewV2State ->
|
||||||
oldState.copy(attachments = attachments, loadState = MediaPreviewV2State.LoadState.READY)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,32 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/core_grey_95"
|
||||||
|
android:theme="@style/TextSecure.MediaPreview">
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
|
android:id="@+id/toolbar_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/media_preview_bar_background"
|
||||||
|
app:elevation="0dp">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/toolbar_cutout_spacer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
android:theme="?actionBarStyle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@android:color/transparent" />
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:id="@+id/media_pager"
|
android:id="@+id/media_pager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
Loading…
Add table
Reference in a new issue